Exemple #1
0
def get_dvh(structure,
            dose,
            roi,
            limit=None,
            calculate_full_volume=True,
            thickness=None,
            callback=None):
    """Calculate a cumulative DVH in Gy from a DICOM RT Structure Set & Dose.

    Parameters
    ----------
    structure : pydicom Dataset
        DICOM RT Structure Set used to determine the structure data.
    dose : pydicom Dataset
        DICOM RT Dose used to determine the dose grid.
    roi : int
        The ROI number used to uniquely identify the structure in the structure
        set.
    limit : int, optional
        Dose limit in cGy as a maximum bin for the histogram.
    calculate_full_volume : bool, optional
        Calculate the full structure volume including contours outside of the
        dose grid.
    thickness : float, optional
        Structure thickness used to calculate volume of a voxel.
    callback : function, optional
        A function that will be called at every iteration of the calculation.
    """
    from dicompylercore import dicomparser
    rtss = dicomparser.DicomParser(structure)
    rtdose = dicomparser.DicomParser(dose)
    structures = rtss.GetStructures()
    s = structures[roi]
    s['planes'] = rtss.GetStructureCoordinates(roi)
    s['thickness'] = thickness if thickness else rtss.CalculatePlaneThickness(
        s['planes'])

    calcdvh = calculate_dvh(s, rtdose, limit, calculate_full_volume, callback)
    return dvh.DVH(counts=calcdvh.histogram,
                   bins=(np.arange(0, 2) if (calcdvh.histogram.size == 1) else
                         np.arange(0, calcdvh.histogram.size + 1) / 100),
                   dvh_type='differential',
                   dose_units='Gy',
                   notes=calcdvh.notes,
                   name=s['name']).cumulative
def get_pixels_hu(im):
    ds = dicomparser.DicomParser(im)
    #intercept, slope = ds.GetRescaleInterceptSlope
    intercept = im.RescaleIntercept
    slope = im.RescaleSlope
    rescale = im.pixel_array * slope + intercept
    window, level = ds.GetDefaultImageWindowLevel()
    pixel = ds.GetLUTValue(rescale, window, level)
    return pixel
Exemple #3
0
    def pluginMenu(self, evt):
        """Import DICOM data quickly."""

        dlg = wx.FileDialog(
            self.parent,
            defaultDir=self.path,
            wildcard="All Files (*.*)|*.*|DICOM File (*.dcm)|*.dcm",
            message="Choose a DICOM File",
        )

        patient = {}
        if dlg.ShowModal() == wx.ID_OK:
            filename = dlg.GetPath()
            # Try to parse the file if is a DICOM file
            try:
                logger.debug("Reading: %s", filename)
                dp = dicomparser.DicomParser(filename)
            # Otherwise show an error dialog
            except (AttributeError, EOFError, IOError, KeyError):
                logger.info("%s is not a valid DICOM file.", filename)
                dlg = wx.MessageDialog(
                    self.parent,
                    filename + " is not a valid DICOM file.",
                    "Invalid DICOM File",
                    wx.OK | wx.ICON_ERROR,
                )
                dlg.ShowModal()
            # If this is really a DICOM file, place it in the appropriate bin
            else:
                if ("ImageOrientationPatient"
                        in dp.ds) and not (dp.ds.Modality in ["RTDOSE"]):
                    patient["images"] = []
                    patient["images"].append(dp.ds)
                elif dp.ds.Modality in ["RTSTRUCT"]:
                    patient["rtss"] = dp.ds
                elif dp.ds.Modality in ["RTPLAN"]:
                    patient["rtplan"] = dp.ds
                elif dp.ds.Modality in ["RTDOSE"]:
                    patient["rtdose"] = dp.ds
                else:
                    patient[dp.ds.Modality] = dp.ds
                # Since we have decided to use this location to import from,
                # update the location in the preferences for the next session
                # if the 'import_location_setting' is "Remember Last Used"
                if self.import_location_setting == "Remember Last Used":
                    pub.sendMessage(
                        "preferences.updated.value",
                        msg={
                            "general.dicom.import_location":
                            dlg.GetDirectory()
                        },
                    )
                    pub.sendMessage("preferences.requested.values",
                                    msg="general.dicom")
        pub.sendMessage("patient.updated.raw_data", msg=patient)
        dlg.Destroy()
        return
Exemple #4
0
    def show(self, z=None):
        """Show the dose grid using Matplotlib if present.

        Parameters
        ----------
        z : float, optional
            slice position to display initially, by default None

        """
        if not mpl_available:
            raise ImportError(
                "Matplotlib could not be loaded. Install and try again.")
            return self
        import matplotlib.pyplot as plt
        from matplotlib.widgets import Slider

        # Extract the list of planes (z) from the dose grid
        planes = (np.array(self.ds.GridFrameOffsetVector) *
                  self.ds.ImageOrientationPatient[0] *
                  self.ds.ImageOrientationPatient[4]
                  ) + self.ds.ImagePositionPatient[2]

        # Set up the plot
        fig = plt.figure()
        ax = fig.add_subplot(111)
        rtdose = dicomparser.DicomParser(self.ds)

        # Get the middle slice if the z is not provided
        z = planes[planes.size // 2] if z is None else z
        zplane = rtdose.GetDoseGrid(z) * self.ds.DoseGridScaling
        # Flag to invert slider min/max if GFOV is decreasing (i.e. FFS)
        reverse = planes[0] > planes[-1]
        im = ax.imshow(
            zplane,
            cmap="jet",
        )

        # Create a slider to change the (z)
        axslice = fig.add_axes([0.34, 0.01, 0.50, 0.02])
        slider = Slider(
            ax=axslice,
            label="Slice Position (mm):",
            valmin=planes[-1] if reverse else planes[0],
            valmax=planes[0] if reverse else planes[-1],
            valinit=z,
            valstep=np.diff(planes)[0],
        )

        def updateslice(z):
            """Update the data to show on the plot."""
            im.set_data(rtdose.GetDoseGrid(z) * self.ds.DoseGridScaling)
            plt.draw()

        slider.on_changed(updateslice)
        plt.show()
        return self
    def get_roi_names(self, mrn):

        structure_file = self.structure[self.mrn.index(mrn)]
        rt_st = dicomparser.DicomParser(structure_file)
        rt_structures = rt_st.GetStructures()

        roi = {}
        for key in list(rt_structures):
            if rt_structures[key]['type'].upper() not in {'MARKER', 'REGISTRATION', 'ISOCENTER'}:
                roi[key] = rt_structures[key]['name']

        return roi
Exemple #6
0
def calDVHmetrics(rtssfile, rtdosefile, outputfile=None):
    RTss = dicomparser.DicomParser(rtssfile)
    #RTdose = dicomparser.DicomParser("testdata/rtdose.dcm")
    RTstructures = RTss.GetStructures()
    patient = rtssfile[rtssfile.rfind('RS.') + 3:rtssfile.rfind('.dcm')]
    calcdvhs = {}
    OARs = ['Liver', 'Spleen', 'Kidney R', 'Kidney L', 'Spinal cord T10 S1']
    strDVH = ""
    for key, structure in RTstructures.iteritems():
        OARname = structure['name'].replace("_", " ")
        if OARname in OARs:
            calcdvhs[key] = dvhcalc.get_dvh(rtssfile, rtdosefile, key)
            #print(structure['name'])
            diff_dvh = calcdvhs[key].differential
            mean_dose = np.sum(
                diff_dvh.counts * diff_dvh.bins[1:]
            ) / diff_dvh.volume  #integra of the differential dvh, and divide the volume
            if len(calcdvhs[key].counts) > 1000:
                V10 = calcdvhs[key].counts[1000] / calcdvhs[key].volume * 100
                V5 = calcdvhs[key].counts[500] / calcdvhs[key].volume * 100
            elif len(calcdvhs[key].counts) > 500:
                V10 = 0
                V5 = calcdvhs[key].counts[500] / calcdvhs[key].volume * 100
            else:
                V10 = 0
                V5 = 0
            if calcdvhs[key].volume > 0:
                v2cc = 2 / calcdvhs[
                    key].volume * 100  # relative volume of 2cc to the OAR
                binsabove2cc = calcdvhs[key].bins[np.where(
                    calcdvhs[key].counts > v2cc)]
                D2cc = calcdvhs[key].bins[np.where(
                    calcdvhs[key].counts > v2cc)][-1]
            else:
                D2cc = 0
            strDVH = strDVH + patient + ', ' + structure['name'] + ', ' + str(
                np.round(calcdvhs[key].volume, 2)) + ', ' + str(
                    np.round(mean_dose, 2)) + ', ' + str(np.round(
                        V5, 2)) + ', ' + str(np.round(V10, 2)) + ', ' + str(
                            np.round(D2cc, 2)) + '\n'
    if outputfile is None:
        print "patient, OAR, volumn(cc), Mean dose(Gy), V5(%), V10(%), D2cc(Gy)\n", strDVH
        return strDVH
    else:
        fl = open(outputfile, "a")
        if os.stat(outputfile).st_size == 0:
            fl.write(
                "patient, OAR, volumn(cc), Mean dose(Gy), V5(%), V10(%), D2cc(Gy)\n"
            )
        fl.write(strDVH)
        fl.close()
        return None
def get_dicom_dose(file):
    """Will return the prescribed dose (in Gy) from an rtplan file. This is needed for DVH analysis.
    An error will be shown to the user if a non RTPlan file is supplied"""
    if dicom_type(file) != 'rtplan':
        print('Prescription cannot be obtained from non RTPlan DICOM file. Returned None.')
        dicom_dose = None
    else:
        if is_oncentra(file) == True: ## oncentra seems to not store the prescribed dose in the same way, so deal with seperately
            print('Oncentra File - Attempting to determine prescription...')
            dicom_dose = get_oncentra_dose(file)
            ## get oncentra dose...
        else:
            dicom_dose = dicomparser.DicomParser(file).GetPlan()['rxdose']/100 ## convert to Gy for use
    return dicom_dose
def get_image(slice):
    """
    Retorna um array de pixels com valores LUT

    :param slice: dataset dicom
    :return pixels_slice:  array numpy com os pixels transformados
    """
    ds = dicomparser.DicomParser(slice)
    intercept, slope = ds.GetRescaleInterceptSlope()
    rescaled_image = slice.pixel_array * slope + intercept
    window, level = ds.GetDefaultImageWindowLevel()
    pixels_slice = ds.GetLUTValue(rescaled_image, window, level)

    return pixels_slice
Exemple #9
0
 def update_plan_structures(self):
     self.structures = dicomparser.DicomParser(
         self.current_struct_file).GetStructures()
     self.roi_keys = [
         key for key in self.structures
         if self.structures[key]['type'].upper() != 'MARKER'
     ]
     self.roi_names = [
         str(self.structures[key]['name']) for key in self.roi_keys
     ]
     self.roi_key_map = {
         name: self.roi_keys[i]
         for i, name in enumerate(self.roi_names)
     }
     self.select_roi.options = [''] + self.roi_names
     self.update_roi_select()
    def buildPlan(self, planRawDataDict):
        planRawData = planRawDataDict.patientBaseDict
        if 'planListRawData' in planRawData:
            for plan in planRawData.planListRawData:
                logging.info('planName:%s', plan.PlanName)
                refImageID = plan.PrimaryCTImageSetID
                refImageHder = None
                refImageInfo = None
                refImageData = None
                for image in planRawData.imageSetListRawData:
                    if image.ImageSetID == refImageID:
                        refImageHder = image.CTHeader
                        refImageInfo = image.CTInfo
                        refImageData = image.CTData
                        break

                setupPosition = None
                roiShiftVector = None
                if refImageHder:
                    setupPosition = refImageHder.patient_position
                    roiShiftVector = self.getStructShift(refImageHder)

                # if 'planPointsRawData' in plan.planData:
                #     pointsDict = self.getPoints(
                #         plan.planData.planPointsRawData, setupPosition, roiShiftVector)

                if 'planROIsRawData' in plan.planData:
                    # contourDict = self.getContours(
                    # plan.planData.planROIsRawData, setupPosition, roiShiftVector)

                    # createStructDS(patientInfoDict, ImageInfoUIDs, planROIsRawData,setupPosition, roiShiftVector)
                    RS_ds = self.createStructDS(plan.planData, refImageInfo,
                                                setupPosition, roiShiftVector)
                    rsObject = dicomparser.DicomParser(RS_ds)
                    structs = rsObject.GetStructures()
                    for (key, Roi) in structs.items():
                        # print('============================')

                        planes = rsObject.GetStructureCoordinates(key)
                        thickness = rsObject.CalculatePlaneThickness(planes)
                        volume = rsObject.CalculateStructureVolume(
                            planes, thickness)
                        logging.info('key=%d,name=%s,volume=%s', key,
                                     Roi['name'], str(volume))

                print('end')
def getImages(datasets):
    """
    Retorna um array de pixels com valores LUT para cada dataset

    :param datasets: lista de datasets dicom
    :return pixels_slice:  lista de arrays numpy com os pixels transformados para
                           cada dataset
    """
    pixels_slice = list()
    for slice in datasets:
        ds = dicomparser.DicomParser(slice)
        intercept, slope = ds.GetRescaleInterceptSlope()
        rescaled_image = slice.pixel_array * slope + intercept
        window, level = ds.GetDefaultImageWindowLevel()
        pixels_slice.append(ds.GetLUTValue(rescaled_image, window, level))

    return pixels_slice
def load_rtstruct(path):
    """
    Carrega o documento de marcação RT STRUCT do volume
    no referente path

    :param path: str indicando o diretório do conjunto
                 de slices DICOM, incluindo o RT STRUCT
    :return rt_struct: uma instância do dicompyler com as
                  marcações
    """
    rt_struct = None

    for root, dirs, files in os.walk(path):
        for file in files:
            ds = pydicom.dcmread(root + '/'+ file)
            if ds.Modality != 'CT':
                rt_struct = dicomparser.DicomParser(ds)

    return rt_struct
Exemple #13
0
 def rebuild_tree_ctrl_rois(self, plan_uid):
     """
     Delete all nodes of current tree_ctrl_rois and build for the specified plan
     :param plan_uid: pydicom ds.SOPInstanceUID for the RT Plan of interest
     """
     self.tree_ctrl_rois.DeleteChildren(self.root_rois)
     if self.dicom_file_paths[plan_uid]['rtstruct'][0]:
         self.tree_ctrl_rois.SetItemBackgroundColour(self.root_rois, None)
         dicom_rt_struct = dicomparser.DicomParser(self.dicom_file_paths[plan_uid]['rtstruct'][0])
         structures = dicom_rt_struct.GetStructures()
         self.roi_name_map = {structures[key]['name']: {'key': key, 'type': structures[key]['type']}
                              for key in list(structures) if structures[key]['type'] != 'MARKER'}
         self.roi_nodes = {}
         rois = list(self.roi_name_map)
         rois.sort()
         for roi in rois:
             self.roi_nodes[roi] = self.tree_ctrl_rois.AppendItem(self.root_rois, roi, ct_type=0)
     else:
         self.tree_ctrl_rois.SetItemBackgroundColour(self.root_rois, wx.Colour(255, 0, 0))
def get_mark(dataset, position, spacing, roi):
    """
    Retorna a marcação de uma região de interesse (ROI) dado um dataset DICOM
    na modalidade RTSTRUCT

    :param dataset: Dataset da modalidade RTSTRUCT 
    :param position: tuple() com coordenadas x,y,z do canto superior esquerdo
                     da imagem
    :param spacing: tuple() com a distância física no paciente entre o centro de cada pixel, 
                    especificado por um par numérico - espaçamento de linhas adjacentes 
                    (delimitador) espaçamento de colunas adjacentes em mm.
    :param roi: str() representando o nome da região de interesse que se deseja obter a marcação

    :return coordinates: list() com indices das marcação na imagem.
    """
    marking = dicomparser.DicomParser(dataset)
    structures = marking.GetStructures()
    roi_numbers = list()
    for i in structures:
        if roi in structures[i]['name']:
            roi_numbers.append(structures[i]['id'])
    if roi_numbers == None:
        raise NameError(roi + " não está entre as estruturas marcadas")

    coordinates = list()
    for roi_number in roi_numbers:
        try:
            for mark in marking.GetStructureCoordinates(roi_number)[
                    str(round(position[2], 2)) + '0']:
                contours = np.array(mark['data'])
                lista = list()
                for c in contours:
                    lista.append(((c[0] - position[0]) / spacing[0],
                                  (c[1] - position[1]) / spacing[1]))
                #rows = ((contour[:, 1] - position[1])/spacing[1]).astype(int)
                #columns = ((contour[:, 0] - position[0])/spacing[0]).astype(int)
                coordinates.append(lista)
        except:
            continue

    return coordinates
def getStandardTemplate(standard_path):
    ###########Suponha que o volume tenha sido selecionado aleatoriamente
    #Gerando o template padrão
    #- Seleciona aleatoriamente um volume dentre todos os outros do banco de dados.

    volume = choice_volume(standard_path)
    quant_slices = len(volume)
    slice = choice(volume[1])
    #- Encontrar a marcação do especialista, encontrar o centro da massa dessa marcação
    marking = dicomparser.DicomParser(volume[0])
    structures = marking.GetStructures()
    number_roi = None
    for i in structures:
        if structures[i]['name'] == 'SpinalCord':
            number_roi = structures[i]['id']


    while True:
        #Sorteia um novo slice caso o slice sorteado não contenha a medula
        try:
            contour = np.array(marking.GetStructureCoordinates(number_roi)['{0:.2f}'.format(slice.ImagePositionPatient[2])][0]['data'])
            break
        except KeyError:
            slice = choice(volume[1])

    rows = ((contour[:, 1] - slice.ImagePositionPatient[1])/slice.PixelSpacing[1]).astype(int)
    columns = ((contour[:, 0] - slice.ImagePositionPatient[0])/slice.PixelSpacing[0]).astype(int)
    #Conseguindo LUT Values
    pixels_slice = getImage(slice)

    diameter_x = rows.max() - rows.min()
    diameter_y = columns.max() - columns.min()
    center_x = int(diameter_x//2 + rows.min())
    center_y = int(diameter_y//2 + columns.min())


    #- Recortar duas vezes o tamanho da região correspondente de todos os lados.
    #print("[{0}:{1}, {2}:{3}]".format(rows.min() - (2*diameter_y), rows.max() + (2*diameter_y), columns.min() - (2*diameter_x), columns.max() + (2*diameter_x)))
    standard_template = pixels_slice[rows.min() - (2*diameter_y): rows.max() + (2*diameter_y), columns.min() - (2*diameter_x):columns.max() + (2*diameter_x)]
    cv2.imwrite("standard_template.png", standard_template)
    return standard_template
def dicom_type(file):
    """ will return 'rtss', 'rtdose', 'rtplan','ct' dependant on type of dicom file"""
    the_type = dicomparser.DicomParser(file).GetSOPClassUID()
    return the_type
Exemple #17
0
 def setUp(self):
     """Setup files for Image modality testing."""
     ct_0_dcm = os.path.join(example_data, "ct.0.dcm")
     self.dp = dicomparser.DicomParser(ct_0_dcm)
Exemple #18
0
 def test_dataset_import(self):
     """Test if a pydicom dataset file can be parsed."""
     dp1 = dicomparser.DicomParser(self.dp.ds)
     self.assertEqual(self.dp.ds, dp1.ds)
Exemple #19
0
 def setUp(self):
     """Setup the files for RT Dose modality testing."""
     rtdose_dcm = os.path.join(example_data, "rtdose.dcm")
     self.dp = dicomparser.DicomParser(rtdose_dcm)
Exemple #20
0
 def setUp(self):
     """Setup the files for RT Structure Set modality testing."""
     rtss_dcm = os.path.join(example_data, "rtss.dcm")
     self.dp = dicomparser.DicomParser(rtss_dcm)
Exemple #21
0
def DirectorySearchThread(path):
    """Search the directory."""
    patients = {}
    patients['path'] = path
    patients['series'] = {}
    # Check if the path is valid
    if os.path.isdir(path):
        files = []
        # 遍历整个目录
        for root, dirs, filenames in os.walk(path):
            files += map(lambda f: os.path.join(root, f), filenames)  # 获得所有的文件
        patients['filearray'] = filenames

        # print("files:{}".format(files))

        for n in range(len(files)):
            if (os.path.isfile(files[n])):
                try:
                    logger.debug("Reading: %s", files[n])
                    dp = dicomparser.DicomParser(files[n])
                except (AttributeError, EOFError, IOError, KeyError):
                    pass
                    logger.info("%s is not a valid DICOM file.", files[n])
                else:
                    patient = dp.GetDemographics()
                    patients['demographics'] = patient
                    # Create each Series of images
                    if (('ImageOrientationPatient' in dp.ds) and \
                            not (dp.GetSOPClassUID() == 'rtdose')):
                        seinfo = dp.GetSeriesInfo()
                        seinfo['numimages'] = 0
                        seinfo['modality'] = dp.ds.SOPClassUID.name
                        if not seinfo['id'] in patients['series']:
                            patients['series'][seinfo['id']] = seinfo
                        if not 'images' in patients:
                            patients['images'] = {}
                        image = {}
                        image['id'] = dp.GetSOPInstanceUID()
                        image['filename'] = files[n]
                        image['series'] = seinfo['id']
                        image['referenceframe'] = dp.GetFrameOfReferenceUID()
                        patients['series'][seinfo['id']]['numimages'] = \
                            patients['series'][seinfo['id']]['numimages'] + 1
                        patients['images'][image['id']] = image
                    # Create each RT Structure Set
                    elif dp.ds.Modality in ['RTSTRUCT']:
                        if not 'structures' in patients:
                            patients['structures'] = {}
                        structure = dp.GetStructureInfo()
                        structure['id'] = dp.GetSOPInstanceUID()
                        structure['filename'] = files[n]
                        structure['series'] = dp.GetReferencedSeries()
                        structure[
                            'referenceframe'] = dp.GetFrameOfReferenceUID()
                        patients['structures'][structure['id']] = structure
                    # Otherwise it is a currently unsupported file
                    else:
                        logger.info("%s is a %s file and is not " + \
                            "currently supported.",
                            files[n], dp.ds.SOPClassUID.name)
        return patients
        # resultFunc(patients)
    # if the path is not valid, display an error message
    else:
        print('No dcm!')
Exemple #22
0
def GetPatientData(patients):
    """Get the data of the selected patient from the DICOM importer dialog."""
    filearray = patients['filearray']
    # print(filearray)
    path = patients['path']
    # print(path)
    for n in range(0, len(filearray)):
        dcmfile = str(os.path.join(path, filearray[n]))
        dp = dicomparser.DicomParser(dcmfile)  # 解析dicom文件
        if (n == 0):
            patient = {}
        if (('ImageOrientationPatient' in dp.ds) and \
            not (dp.GetSOPClassUID() == 'rtdose')):
            if not 'images' in patient:
                patient['images'] = []
            try:
                dp.ds.file_meta.TransferSyntaxUID
            except:
                dp.ds.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
            patient['images'].append(dp.ds)
            # print(dp.ds.pixel_array.shape)


#            patient['images'][n-1].pixel_array = np.array([bytearray(patient['images'][n-1].PixelData)])\
#.view(dtype = np.uint16).reshape(patient['images'][n-1].Rows, patient['images'][n-1].Columns)
        elif (dp.ds.Modality in ['RTSTRUCT']):
            patient['rtss'] = dp.ds
    # Sort the images based on a sort descriptor:
    # (ImagePositionPatient, InstanceNumber or AcquisitionNumber)
    if 'images' in patient:
        sortedimages = []
        unsortednums = []
        sortednums = []
        images = patient['images']
        sort = 'IPP'
        # Determine if all images in the series are parallel
        # by testing for differences in ImageOrientationPatient
        parallel = True
        for i, item in enumerate(images):
            if (i > 0):
                iop0 = np.array(item.ImageOrientationPatient)
                iop1 = np.array(images[i - 1].ImageOrientationPatient)
                if (np.any(np.array(np.round(iop0 - iop1), dtype=np.int32))):
                    parallel = False
                    break
                # Also test ImagePositionPatient, as some series
                # use the same patient position for every slice
                ipp0 = np.array(item.ImagePositionPatient)
                ipp1 = np.array(images[i - 1].ImagePositionPatient)
                if not (np.any(np.array(np.round(ipp0 - ipp1),
                                        dtype=np.int32))):
                    parallel = False
                    break
        # If the images are parallel, sort by ImagePositionPatient
        if parallel:
            sort = 'IPP'
        else:
            # Otherwise sort by Instance Number
            if not (images[0].InstanceNumber == \
            images[1].InstanceNumber):
                sort = 'InstanceNumber'
            # Otherwise sort by Acquisition Number
            elif not (images[0].AcquisitionNumber == \
            images[1].AcquisitionNumber):
                sort = 'AcquisitionNumber'

        # Add the sort descriptor to a list to be sorted
        for i, image in enumerate(images):
            if (sort == 'IPP'):
                unsortednums.append(image.ImagePositionPatient[2])
            else:
                unsortednums.append(image.data_element(sort).value)

        # Sort image numbers in descending order for head first patients
        if ('hf' in image.PatientPosition.lower()) and (sort == 'IPP'):
            sortednums = sorted(unsortednums, reverse=True)
        # Otherwise sort image numbers in ascending order
        else:
            sortednums = sorted(unsortednums)

        # Add the images to the array based on the sorted order
        for s, slice in enumerate(sortednums):
            for i, image in enumerate(images):
                if (sort == 'IPP'):
                    if (slice == image.ImagePositionPatient[2]):
                        sortedimages.append(image)
                elif (slice == image.data_element(sort).value):
                    sortedimages.append(image)

        # Save the images back to the patient dictionary
        patient['images'] = sortedimages
        return patient
Exemple #23
0
    def get_patient_data(self, rxdose):
        """Get the data of the selected patient from the DICOM importer dialog."""
        for n in range(0, len(self.filearray)):
            dcmfile = str(os.path.join(self.path, self.filearray[n]))
            dp = dicomparser.DicomParser(dcmfile)
            if (n == 0):
                self.patient = {}
                self.patient['rxdose'] = rxdose
            if (('ImageOrientationPatient' in dp.ds)
                    and not (dp.GetSOPClassUID() == 'rtdose')):
                if not 'images' in self.patient:
                    self.patient['images'] = []
                self.patient['images'].append(dp.ds)
            elif (dp.ds.Modality in ['RTSTRUCT']):
                self.patient['rtss'] = dp.ds
            elif (dp.ds.Modality in ['RTPLAN']):
                self.patient['rtplan'] = dp.ds
            elif (dp.ds.Modality in ['RTDOSE']):
                self.patient['rtdose'] = dp.ds
        # Sort the images based on a sort descriptor:
        # (ImagePositionPatient, InstanceNumber or AcquisitionNumber)
        if 'images' in self.patient:
            sortedimages = []
            unsortednums = []
            sortednums = []
            images = self.patient['images']
            sort = 'IPP'
            # Determine if all images in the series are parallel
            # by testing for differences in ImageOrientationPatient
            parallel = True
            for i, item in enumerate(images):
                if (i > 0):
                    iop0 = np.array(item.ImageOrientationPatient)
                    iop1 = np.array(images[i - 1].ImageOrientationPatient)
                    if (np.any(np.array(np.round(iop0 - iop1),
                                        dtype=np.int32))):
                        parallel = False
                        break
                    # Also test ImagePositionPatient, as some series
                    # use the same patient position for every slice
                    ipp0 = np.array(item.ImagePositionPatient)
                    ipp1 = np.array(images[i - 1].ImagePositionPatient)
                    if not (np.any(
                            np.array(np.round(ipp0 - ipp1), dtype=np.int32))):
                        parallel = False
                        break
            # If the images are parallel, sort by ImagePositionPatient
            if parallel:
                sort = 'IPP'
            else:
                # Otherwise sort by Instance Number
                if not (images[0].InstanceNumber == images[1].InstanceNumber):
                    sort = 'InstanceNumber'
                # Otherwise sort by Acquisition Number
                elif not (images[0].AcquisitionNumber
                          == images[1].AcquisitionNumber):
                    sort = 'AcquisitionNumber'

            # Add the sort descriptor to a list to be sorted
            for i, image in enumerate(images):
                if (sort == 'IPP'):
                    unsortednums.append(image.ImagePositionPatient[2])
                else:
                    unsortednums.append(image.data_element(sort).value)

            # Sort image numbers in descending order for head first patients
            if ('hf' in image.PatientPosition.lower()) and (sort == 'IPP'):
                sortednums = sorted(unsortednums, reverse=True)
            # Otherwise sort image numbers in ascending order
            else:
                sortednums = sorted(unsortednums)

            # Add the images to the array based on the sorted order
            for s, slice in enumerate(sortednums):
                for i, image in enumerate(images):
                    if (sort == 'IPP'):
                        if (slice == image.ImagePositionPatient[2]):
                            sortedimages.append(image)
                    elif (slice == image.data_element(sort).value):
                        sortedimages.append(image)

            # Save the images back to the patient dictionary
            self.patient['images'] = sortedimages
Exemple #24
0
    def parse_patient_data(self):
        """Thread to load the patient data."""
        ptdata = self.patient
        patient = {}
        pbar = tqdm(total=100)
        if not 'images' in ptdata:
            # Look for DICOM data in the ptdata dictionary
            for rtdatatype in ptdata.keys():
                if isinstance(ptdata[rtdatatype], pydicom.dataset.FileDataset):
                    patient.update(
                        dicomparser.DicomParser(
                            ptdata[rtdatatype]).GetDemographics())
                    break
        if 'rtss' in ptdata:
            # pbar.update(20)
            pbar.n = 20
            pbar.set_description('Processing RT Structure Set...')
            pbar.refresh()

            d = dicomparser.DicomParser(ptdata['rtss'])
            s = d.GetStructures()
            for k in s.keys():
                s[k]['planes'] = d.GetStructureCoordinates(k)
                s[k]['thickness'] = d.CalculatePlaneThickness(s[k]['planes'])
            patient['structures'] = s
        if 'rtplan' in ptdata:
            pbar.n = 40
            pbar.refresh()
            pbar.set_description('Processing RT Plan...')
            patient['plan'] = dicomparser.DicomParser(
                ptdata['rtplan']).GetPlan()
        if 'rtdose' in ptdata:
            pbar.n = 60
            pbar.set_description('Processing RT Dose...')
            pbar.refresh()
            patient['dvhs'] = dicomparser.DicomParser(
                ptdata['rtdose']).GetDVHs()
            patient['dose'] = dicomparser.DicomParser(ptdata['rtdose'])
        if 'images' in ptdata:
            pbar.n = 80
            pbar.set_description('Processing Images...')
            pbar.refresh()
            if not 'id' in patient:
                patient.update(
                    dicomparser.DicomParser(
                        ptdata['images'][0]).GetDemographics())
            patient['images'] = []
            for image in ptdata['images']:
                patient['images'].append(dicomparser.DicomParser(image))
        if 'rxdose' in ptdata:
            if not 'plan' in patient:
                patient['plan'] = {}
            patient['plan']['rxdose'] = ptdata['rxdose']
        # if the min/max/mean dose was not present, calculate it and save it for each structure
        pbar.n = 90
        pbar.set_description('Processing DVH data...')
        pbar.refresh()
        if ('dvhs' in patient) and ('structures' in patient):
            # If the DVHs are not present, calculate them
            i = 0
            for key, structure in patient['structures'].items():
                # Only calculate DVHs if they are not present for the structure
                # or recalc all DVHs if the preference is set
                if ((not (key in patient['dvhs'].keys()))):
                    # Only calculate DVHs for structures, not applicators
                    # and only if the dose grid is present
                    if ((structure['name'].startswith('Applicator'))
                            or (not "PixelData" in patient['dose'].ds)):
                        continue
                    pbar.n = int(np.round(
                        10 * i / len(patient['structures']))) + 90
                    pbar.set_description('Calculating DVH for ' +
                                         structure['name'] + '...')
                    pbar.refresh()
                    # Limit DVH bins to 500 Gy due to high doses in brachy
                    dvh = dvhcalc.get_dvh(ptdata['rtss'], patient['dose'].ds,
                                          key, 50000)
                    if len(dvh.counts):
                        patient['dvhs'][key] = dvh
                    i += 1
            for key, dvh in patient['dvhs'].items():
                dvh.rx_dose = patient['plan']['rxdose'] / 100
        pbar.n = 100
        pbar.set_description('Done')
        pbar.close()
        self.parse_patient = patient
def get_pt_id(file):
    """Get patient ID from dicom file"""
    pt_info = dicomparser.DicomParser(file).GetDemographics()
    return pt_info['id']
Exemple #26
0
    def plot(self):

        #retrieve plan data & design DVH window
        plan1files = float(self.box5.get())
        plan2files = float(self.box6.get())
        self.newWindow = Toplevel(
            self.window)  #initiate new window for summed DVH
        self.newWindow.title("Recalculated DVH")
        #self.newWindow.iconbitmap("C:/Users/Owner/Documents/Rlogo2.ico")

        rtssfile1 = self.filename5  #structure file
        RTss1 = dicomparser.DicomParser(
            rtssfile1)  #read through structure file
        RTstructures1 = RTss1.GetStructures(
        )  #get each individual structure information

        #at least one dose file required for plan 1 and plan 2
        rtdosefile1 = self.filename1
        rtdosefile5 = self.filename6
        #conditional statements for multiple dose file input
        if plan1files >= 2:
            rtdosefile2 = self.filename2
        if plan1files >= 3:
            rtdosefile3 = self.filename3
        if plan1files >= 4:
            rtdosefile4 = self.filename4
        if plan2files >= 2:
            rtdosefile6 = self.filename7
        if plan2files >= 3:
            rtdosefile7 = self.filename8
        if plan2files >= 4:
            rtdosefile8 = self.filename9

        #structure to be analysed
        enteredtext = str(self.box4.get())

        #EQD2 parameters
        dpf1 = float(self.box1.get())  #plan 1 dose per fraction
        dpf2 = float(self.box2.get())  #plan 2 dose per fraction
        abratio = float(self.box3.get())  # tissue-specific alpha/beta ratio

        #EQD2 equation
        x1 = np.array((dpf1 + abratio) / (float(2.0) + abratio))
        x2 = np.array((dpf2 + abratio) / (float(2.0) + abratio))

        # Generation of the calculated DVHs
        #initiation of empty arrays to fill with correct structure data
        calcdvhs1 = {}
        calcdvhs2 = {}
        calcdvhs3 = {}
        calcdvhs4 = {}
        calcdvhs5 = {}
        calcdvhs6 = {}
        calcdvhs7 = {}
        calcdvhs8 = {}

        print("RED is calculating your EQD2 DVH....")
        print("Please wait, this could take a few moments")
        #iterate through dose file 1 to find correct stucture data
        for key, structure in RTstructures1.items():
            calcdvhs1[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile1, key)
            if (key in calcdvhs1) and (structure['name'] == enteredtext) and (
                    len(calcdvhs1[key].counts)
                    and calcdvhs1[key].counts[0] != 0):
                print('1st Plan 1 DVH found for ' + structure['name'])
                data1 = np.array(calcdvhs1[key].bins) * x1
                lastdata1 = data1[-1]
                vola = calcdvhs1[key].counts * 100 / calcdvhs1[key].counts[0]

        # iterate through dose file 2 to find correct stucture data
        if plan1files >= 2:
            for key, structure in RTstructures1.items():
                calcdvhs2[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile2, key)
                if (key in calcdvhs2) and (structure['name']
                                           == enteredtext) and (
                                               len(calcdvhs2[key].counts) and
                                               calcdvhs2[key].counts[0] != 0):
                    print('2nd Plan 1 DVH found for ' + structure['name'])
                    data2 = np.array(calcdvhs2[key].bins) * x1
                    lastdata2 = data2[-1]
                    volb = calcdvhs2[key].counts * 100 / calcdvhs2[key].counts[
                        0]

        # iterate through dose file 3 to find correct stucture data
        if plan1files >= 3:
            for key, structure in RTstructures1.items():
                calcdvhs3[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile3, key)
                if (key in calcdvhs3) and (structure['name']
                                           == enteredtext) and (
                                               len(calcdvhs3[key].counts) and
                                               calcdvhs3[key].counts[0] != 0):
                    print('3rd Plan 1 DVH found for ' + structure['name'])
                    data3 = np.array(calcdvhs3[key].bins) * x1
                    lastdata3 = data3[-1]
                    volc = calcdvhs3[key].counts * 100 / calcdvhs3[key].counts[
                        0]

        # iterate through dose file 4 to find correct stucture data
        if plan1files >= 4:
            for key, structure in RTstructures1.items():
                calcdvhs4[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile4, key)
                if (key in calcdvhs4) and (structure['name']
                                           == enteredtext) and (
                                               len(calcdvhs4[key].counts) and
                                               calcdvhs4[key].counts[0] != 0):
                    print('4th Plan 1 DVH found for ' + structure['name'])
                    data4 = np.array(calcdvhs4[key].bins) * x1
                    lastdata4 = data4[-1]
                    vold = calcdvhs4[key].counts * 100 / calcdvhs4[key].counts[
                        0]

        # iterate through dose file 5 to find correct stucture data
        for key, structure in RTstructures1.items():
            calcdvhs5[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile5, key)
            if (key in calcdvhs5) and (structure['name'] == enteredtext) and (
                    len(calcdvhs5[key].counts)
                    and calcdvhs5[key].counts[0] != 0):
                print('1st Plan 2 DVH found for ' + structure['name'])
                data5 = np.array(calcdvhs5[key].bins) * x2
                lastdata5 = data5[-1]
                vole = calcdvhs5[key].counts * 100 / calcdvhs5[key].counts[0]

        # iterate through dose file 6 to find correct stucture data
        if plan2files >= 2:
            for key, structure in RTstructures1.items():
                calcdvhs6[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile6, key)
                if (key in calcdvhs6) and (structure['name']
                                           == enteredtext) and (
                                               len(calcdvhs6[key].counts) and
                                               calcdvhs6[key].counts[0] != 0):
                    print('2nd Plan 2 DVH found for ' + structure['name'])
                    data6 = np.array(calcdvhs6[key].bins) * x2
                    lastdata6 = data6[-1]
                    volf = calcdvhs6[key].counts * 100 / calcdvhs6[key].counts[
                        0]

        # iterate through dose file 7 to find correct stucture data
        if plan2files >= 3:
            for key, structure in RTstructures1.items():
                calcdvhs7[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile7, key)
                if (key in calcdvhs7) and (structure['name']
                                           == enteredtext) and (
                                               len(calcdvhs7[key].counts) and
                                               calcdvhs7[key].counts[0] != 0):
                    print('3rd Plan 2 DVH found for ' + structure['name'])
                    data7 = np.array(calcdvhs7[key].bins) * x2
                    lastdata7 = data7[-1]
                    volg = calcdvhs7[key].counts * 100 / calcdvhs7[key].counts[
                        0]

        # iterate through dose file 8 to find correct stucture data
        if plan2files >= 4:
            for key, structure in RTstructures1.items():
                calcdvhs8[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile8, key)
                if (key in calcdvhs8) and (structure['name']
                                           == enteredtext) and (
                                               len(calcdvhs8[key].counts) and
                                               calcdvhs8[key].counts[0] != 0):
                    print('4th Plan 2 DVH found for ' + structure['name'])
                    data8 = np.array(calcdvhs8[key].bins) * x2
                    lastdata8 = data8[-1]
                    volh = calcdvhs8[key].counts * 100 / calcdvhs8[key].counts[
                        0]

        #initiation of DVH window
        fig = Figure()
        ax = fig.add_subplot(111)

        #addition of plan 1 dose data
        if plan1files == 1:
            totaldose1 = lastdata1
        if plan1files == 2:
            totaldose1 = lastdata1 + lastdata2
        if plan1files == 3:
            totaldose1 = lastdata1 + lastdata2 + lastdata3
        if plan1files == 4:
            totaldose1 = lastdata1 + lastdata2 + lastdata3 + lastdata4

        #addition of plan 2 dose data
        if plan2files == 1:
            totaldose2 = lastdata5
        if plan2files == 2:
            totaldose2 = lastdata5 + lastdata6
        if plan2files == 3:
            totaldose2 = lastdata5 + lastdata6 + lastdata7
        if plan2files == 4:
            totaldose2 = lastdata5 + lastdata6 + lastdata7 + lastdata8

        #conditional statements for plan 1 based on number of dose files
        #definition of volume and dose data
        if plan1files == 1:
            y = vola  #volume data
            y1len = len(y)
            x = np.linspace(0, totaldose1, num=y1len)  #dose data
        elif plan1files == 2:
            interyvaluesplan1b = np.concatenate((vola, volb),
                                                axis=0)  #summed volume data
            sorty21 = np.sort(interyvaluesplan1b
                              )  #sorted volume data array in ascending order
            vol21 = sorty21[::
                            -1]  #volume data array reversed to descending order
            y = vol21  #volume data
            y1len = len(y)
            x = np.linspace(0, totaldose1, num=y1len)  #dose data
        elif plan1files == 3:
            interyvalues1plan1a = np.concatenate((vola, volb), axis=0)
            interyvaluesplan1c = np.concatenate((interyvalues1plan1a, volc),
                                                axis=0)  #sumed volume data
            sorty31 = np.sort(interyvaluesplan1c
                              )  #sorted volume data array in ascending order
            vol31 = sorty31[::
                            -1]  #volume data array reversed to descending order
            y = vol31  #volume data
            y1len = len(y)
            x = np.linspace(0, totaldose1, num=y1len)  #dose data
        elif plan1files == 4:
            interyvalues1plan1b = np.concatenate((vola, volb), axis=0)
            interyvalues2plan1a = np.concatenate((volc, vold), axis=0)
            interyvaluesplan1d = np.concatenate(
                (interyvalues1plan1b, interyvalues2plan1a),
                axis=0)  #summed volume data
            sorty41 = np.sort(interyvaluesplan1d
                              )  #sorted volume data array in ascending order
            vol41 = sorty41[::
                            -1]  #volume data array reversed to descending order
            y = vol41
            y1len = len(y)
            x = np.linspace(0, totaldose1, num=y1len)  #dose data
        else:
            print("please check code starting at line 367")

        if plan2files == 1:
            b = vole  #volume data
            blen = len(b)
            a = np.linspace(0, totaldose2, num=blen)  #dose data

        elif plan2files == 2:
            interyvaluesplan2b = np.concatenate((vole, volf),
                                                axis=0)  #summed volume data
            sorty22 = np.sort(interyvaluesplan2b
                              )  # sorted volume data array in ascending order
            vol22 = sorty22[::
                            -1]  #volume data array reversed to descending order
            b = vol22
            blen = len(b)
            a = np.linspace(0, totaldose2, num=blen)  #dose data

        elif plan2files == 3:
            interyvalues1plan2a = np.concatenate((vole, volf), axis=0)
            interyvaluesplan2c = np.concatenate((interyvalues1plan2a, volg),
                                                axis=0)  #summed volume data
            sorty32 = np.sort(interyvaluesplan2c
                              )  #sorted volume data array in ascending order
            vol32 = sorty32[::
                            -1]  #volume data array reversed to descending order
            b = vol32
            blen = len(b)
            a = np.linspace(0, totaldose2, num=blen)  #dose data

        elif plan2files == 4:
            interyvalues1plan2b = np.concatenate((vole, volf), axis=0)
            interyvalues2plan2a = np.concatenate((volg, volh), axis=0)
            interyvaluesplan2d = np.concatenate(
                (interyvalues1plan2b, interyvalues2plan2a),
                axis=0)  #summed volume data
            sorty42 = np.sort(interyvaluesplan2d
                              )  #sorted volume data array in ascending order
            vol42 = sorty42[::
                            -1]  # volume data array reversed to descending order
            b = vol42
            blen = len(b)
            a = np.linspace(0, totaldose2, num=blen)  #dose data
        else:
            print("please check code starting at line 398")

        #plot plan 1 and plan 2 re-calculated DVH
        ax.plot(x, y, c='b')
        ax.plot(a, b, c='g')
        array = np.linspace(0, 100, 9000, endpoint=False)
        # get values from plan1 graph
        line1 = ax.lines[0]
        line1.get_xydata()
        xdat1 = line1.get_xdata()  #get x data
        fp1 = xdat1[::
                    -1]  # reverses x values to match reversed y values in array#
        ydat1 = line1.get_ydata()  #get y data
        xp1 = ydat1[::
                    -1]  # reverses y values from decreasng to increasing so interpolation function can work

        # get values from plan2 graph
        line2 = ax.lines[1]
        line2.get_xydata()
        xdat2 = line2.get_xdata()  #get xdata
        fp2 = xdat2[::
                    -1]  # reverses x values to match reversed y values in array
        ydat2 = line2.get_ydata()  #get y data
        xp2 = ydat2[::
                    -1]  # reverses y values from decreasng to increasing so interpolation function can work

        # set volume array to use for dose interpolation
        inter1 = np.interp([array], xp1, fp1)  # interpolation of plan1 dose
        reshapeinter1 = np.reshape(inter1, (9000, 1))
        inter2 = np.interp([array], xp2, fp2)  # interpolation of plan2 dose
        reshapeinter2 = np.reshape(inter2, (9000, 1))
        xvalues = reshapeinter1 + reshapeinter2  # adding plan1 and plan2 dose
        reshapearray = np.reshape(
            array, (9000, 1))  # array of specified %volume intervals

        #define strings
        dpf12 = str(dpf1)
        dpf21 = str(dpf2)
        abdisplay = str(abratio)

        #plot summed re-calculated DVH in seperate window
        plt.plot(xvalues, reshapearray, c='r')
        plt.title("Recalculated Summed DVH")
        # plt.iconbitmap("C:/Users/Owner/Documents/Rlogo2.ico")
        plt.title("Summed EQD2 DVH for " + enteredtext + " a/b=" + abdisplay +
                  " dpf Plan 1=" + dpf12 + " dpf Plan 2=" + dpf21)
        ax.legend(["Plan 1", "Plan 2"])
        ax.set_title("EQD2 DVH for " + enteredtext + " a/b=" + abdisplay +
                     " dpf Plan 1=" + dpf12 + " dpf Plan 2=" + dpf21)
        ax.set_ylabel("Volume %", fontsize=14)
        ax.set_xlabel("EQD2 Gy", fontsize=14)
        plt.legend(["Plan 1 + 2"])
        plt.ylabel("Volume %", fontsize=14)
        plt.xlabel("EQD2 Gy", fontsize=14)
        ax.set_axisbelow(True)

        #design of summed dvh
        plt.grid(color='gray', linestyle='dashed')
        datacursor()
        ax.yaxis.grid(color='gray', linestyle='dashed')
        ax.xaxis.grid(color='gray', linestyle='dashed')

        #draw sumed DVH
        canvas = FigureCanvasTkAgg(fig, master=self.newWindow)
        canvas.get_tk_widget().grid(row=10)
        canvas.draw()
        plt.show()

        #initiate toolbar for analysis
        toolbarFrame = Frame(master=self.newWindow)
        toolbarFrame.grid(row=20)
        toolbar = NavigationToolbar2Tk(canvas, toolbarFrame)
        toolbar.draw()
Exemple #27
0
    def GetPatientData(self, path, filearray, RxDose, terminate, progressFunc):
        """Get the data of the selected patient from the DICOM importer dialog."""

        wx.CallAfter(progressFunc, -1, 100, "Importing patient. Please wait...")
        for n in range(0, len(filearray)):
            if terminate():
                wx.CallAfter(progressFunc, 98, 100, "Importing patient cancelled.")
                return
            dcmfile = str(os.path.join(self.path, filearray[n]))
            dp = dicomparser.DicomParser(dcmfile)
            if n == 0:
                self.patient = {}
                self.patient["rxdose"] = RxDose
            if ("ImageOrientationPatient" in dp.ds) and not (
                dp.GetSOPClassUID() == "rtdose"
            ):
                if not "images" in self.patient:
                    self.patient["images"] = []
                self.patient["images"].append(dp.ds)
            elif dp.ds.Modality in ["RTSTRUCT"]:
                self.patient["rtss"] = dp.ds
            elif dp.ds.Modality in ["RTPLAN"]:
                self.patient["rtplan"] = dp.ds
            elif dp.ds.Modality in ["RTDOSE"]:
                self.patient["rtdose"] = dp.ds
            wx.CallAfter(
                progressFunc, n, len(filearray), "Importing patient. Please wait..."
            )
        # Sort the images based on a sort descriptor:
        # (ImagePositionPatient, InstanceNumber or AcquisitionNumber)
        if "images" in self.patient:
            sortedimages = []
            unsortednums = []
            sortednums = []
            images = self.patient["images"]
            sort = "IPP"
            # Determine if all images in the series are parallel
            # by testing for differences in ImageOrientationPatient
            parallel = True
            for i, item in enumerate(images):
                if i > 0:
                    iop0 = np.array(item.ImageOrientationPatient)
                    iop1 = np.array(images[i - 1].ImageOrientationPatient)
                    if np.any(np.array(np.round(iop0 - iop1), dtype=np.int32)):
                        parallel = False
                        break
                    # Also test ImagePositionPatient, as some series
                    # use the same patient position for every slice
                    ipp0 = np.array(item.ImagePositionPatient)
                    ipp1 = np.array(images[i - 1].ImagePositionPatient)
                    if not (np.any(np.array(np.round(ipp0 - ipp1), dtype=np.int32))):
                        parallel = False
                        break
            # If the images are parallel, sort by ImagePositionPatient
            if parallel:
                sort = "IPP"
            else:
                # Otherwise sort by Instance Number
                if not (images[0].InstanceNumber == images[1].InstanceNumber):
                    sort = "InstanceNumber"
                # Otherwise sort by Acquisition Number
                elif not (images[0].AcquisitionNumber == images[1].AcquisitionNumber):
                    sort = "AcquisitionNumber"

            logger.debug(f"parallel: {parallel}, sort: {sort}")
            # Add the sort descriptor to a list to be sorted
            for i, image in enumerate(images):
                if sort == "IPP":
                    unsortednums.append(image.ImagePositionPatient[2])
                else:
                    unsortednums.append(image.data_element(sort).value)

            # Sort image numbers in descending order for head first patients
            if ("hf" in image.PatientPosition.lower()) and (sort == "IPP"):
                sortednums = sorted(unsortednums, reverse=True)
            # Otherwise sort image numbers in ascending order
            else:
                sortednums = sorted(unsortednums)

            # Add the images to the array based on the sorted order
            for s, slice in enumerate(sortednums):
                for i, image in enumerate(images):
                    if sort == "IPP":
                        if slice == image.ImagePositionPatient[2]:
                            sortedimages.append(image)
                    elif slice == image.data_element(sort).value:
                        sortedimages.append(image)

            # Save the images back to the patient dictionary
            self.patient["images"] = sortedimages
        wx.CallAfter(progressFunc, 98, 100, "Importing patient complete.")
Exemple #28
0
    def DirectorySearchThread(
        self, parent, path, subfolders, terminate, progressFunc, foundFunc, resultFunc
    ):
        """Thread to start the directory search."""

        # Call the progress function to update the gui
        wx.CallAfter(progressFunc, 0, 0, "Searching for patients...")

        patients = {}

        # Check if the path is valid
        if os.path.isdir(path):

            files = []
            for root, dirs, filenames in os.walk(path):
                files += map(lambda f: os.path.join(root, f), filenames)
                if self.import_search_subfolders == False:
                    break
            for n in range(len(files)):

                # terminate the thread if the value has changed
                # during the loop duration
                if terminate():
                    wx.CallAfter(progressFunc, 0, 0, "Search terminated.")
                    return

                if os.path.isfile(files[n]):
                    try:
                        logger.debug("Reading: %s", files[n])
                        dp = dicomparser.DicomParser(files[n])
                    except (AttributeError, EOFError, IOError, KeyError):
                        pass
                        logger.info("%s is not a valid DICOM file.", files[n])
                    else:
                        patient = dp.GetDemographics()
                        h = hashlib.sha1(patient["id"].encode("utf-8")).hexdigest()
                        if not h in patients:
                            patients[h] = {}
                            patients[h]["demographics"] = patient
                            if not "studies" in patients[h]:
                                patients[h]["studies"] = {}
                                patients[h]["series"] = {}
                            wx.CallAfter(foundFunc, patient)
                        # Create each Study but don't create one for RT Dose
                        # since some vendors use incorrect StudyInstanceUIDs
                        if not (dp.GetSOPClassUID() == "rtdose"):
                            stinfo = dp.GetStudyInfo()
                            if not stinfo["id"] in patients[h]["studies"]:
                                patients[h]["studies"][stinfo["id"]] = stinfo
                        # Create each Series of images
                        if ("ImageOrientationPatient" in dp.ds) and not (
                            dp.GetSOPClassUID() == "rtdose"
                        ):
                            seinfo = dp.GetSeriesInfo()
                            seinfo["numimages"] = 0
                            seinfo["modality"] = dp.ds.SOPClassUID.name
                            if not seinfo["id"] in patients[h]["series"]:
                                patients[h]["series"][seinfo["id"]] = seinfo
                            if not "images" in patients[h]:
                                patients[h]["images"] = {}
                            image = {}
                            image["id"] = dp.GetSOPInstanceUID()
                            image["filename"] = files[n]
                            image["series"] = seinfo["id"]
                            image["referenceframe"] = dp.GetFrameOfReferenceUID()
                            patients[h]["series"][seinfo["id"]]["numimages"] = (
                                patients[h]["series"][seinfo["id"]]["numimages"] + 1
                            )
                            patients[h]["images"][image["id"]] = image
                        # Create each RT Structure Set
                        elif dp.ds.Modality in ["RTSTRUCT"]:
                            if not "structures" in patients[h]:
                                patients[h]["structures"] = {}
                            structure = dp.GetStructureInfo()
                            structure["id"] = dp.GetSOPInstanceUID()
                            structure["filename"] = files[n]
                            structure["series"] = dp.GetReferencedSeries()
                            structure["referenceframe"] = dp.GetFrameOfReferenceUID()
                            patients[h]["structures"][structure["id"]] = structure
                        # Create each RT Plan
                        elif dp.ds.Modality in ["RTPLAN"]:
                            if not "plans" in patients[h]:
                                patients[h]["plans"] = {}
                            plan = dp.GetPlan()
                            plan["id"] = dp.GetSOPInstanceUID()
                            plan["filename"] = files[n]
                            plan["series"] = dp.ds.SeriesInstanceUID
                            plan["referenceframe"] = dp.GetFrameOfReferenceUID()
                            plan["beams"] = dp.GetReferencedBeamsInFraction()
                            plan["rtss"] = dp.GetReferencedStructureSet()
                            patients[h]["plans"][plan["id"]] = plan
                        # Create each RT Dose
                        elif dp.ds.Modality in ["RTDOSE"]:
                            if not "doses" in patients[h]:
                                patients[h]["doses"] = {}
                            dose = {}
                            dose["id"] = dp.GetSOPInstanceUID()
                            dose["filename"] = files[n]
                            dose["referenceframe"] = dp.GetFrameOfReferenceUID()
                            dose["hasdvh"] = dp.HasDVHs()
                            dose["hasgrid"] = "PixelData" in dp.ds
                            dose["summationtype"] = dp.ds.DoseSummationType
                            dose["beam"] = dp.GetReferencedBeamNumber()
                            dose["rtss"] = dp.GetReferencedStructureSet()
                            dose["rtplan"] = dp.GetReferencedRTPlan()
                            patients[h]["doses"][dose["id"]] = dose
                        # Otherwise it is a currently unsupported file
                        else:
                            logger.info(
                                "%s is a %s file and is not " + "currently supported.",
                                files[n],
                                dp.ds.SOPClassUID.name,
                            )

                # Call the progress function to update the gui
                wx.CallAfter(progressFunc, n, len(files), "Searching for patients...")

            if len(patients) == 0:
                progressStr = "Found 0 patients."
            elif len(patients) == 1:
                progressStr = "Found 1 patient. Reading DICOM data..."
            elif len(patients) > 1:
                progressStr = (
                    "Found " + str(len(patients)) + " patients. Reading DICOM data..."
                )
            wx.CallAfter(progressFunc, 0, 1, progressStr)
            wx.CallAfter(resultFunc, patients)

        # if the path is not valid, display an error message
        else:
            wx.CallAfter(progressFunc, 0, 0, "Select a valid location.")
            dlg = wx.MessageDialog(
                parent,
                "The DICOM import location does not exist. Please select a valid location.",
                "Invalid DICOM Import Location",
                wx.OK | wx.ICON_ERROR,
            )
            dlg.ShowModal()
Exemple #29
0
def get_dvh(structure,
            dose,
            roi,
            limit=None,
            calculate_full_volume=True,
            use_structure_extents=False,
            interpolation_resolution=None,
            interpolation_segments_between_planes=0,
            thickness=None,
            memmap_rtdose=False,
            callback=None):
    """Calculate a cumulative DVH in Gy from a DICOM RT Structure Set & Dose.

    Parameters
    ----------
    structure : pydicom Dataset or filename
        DICOM RT Structure Set used to determine the structure data.
    dose : pydicom Dataset or filename
        DICOM RT Dose used to determine the dose grid.
    roi : int
        The ROI number used to uniquely identify the structure in the structure
        set.
    limit : int, optional
        Dose limit in cGy as a maximum bin for the histogram.
    calculate_full_volume : bool, optional
        Calculate the full structure volume including contours outside of the
        dose grid.
    use_structure_extents : bool, optional
        Limit the DVH calculation to the in-plane structure boundaries.
    interpolation_resolution : tuple or float, optional
        Resolution in mm (row, col) to interpolate structure and dose data to.
        If float is provided, original dose grid pixel spacing must be square.
    interpolation_segments_between_planes : integer, optional
        Number of segments to interpolate between structure slices.
    thickness : float, optional
        Structure thickness used to calculate volume of a voxel.
    memmap_rtdose : bool, optional
        Use memory mapping to access the pixel array of the DICOM RT Dose.
        This reduces memory usage at the expense of increased calculation time.
    callback : function, optional
        A function that will be called at every iteration of the calculation.

    Returns
    -------
    dvh.DVH
        An instance of dvh.DVH in cumulative dose. This can be converted to
        different formats using the attributes and properties of the DVH class.
    """
    from dicompylercore import dicomparser

    rtss = dicomparser.DicomParser(structure)
    rtdose = dicomparser.DicomParser(dose, memmap_pixel_array=memmap_rtdose)
    structures = rtss.GetStructures()
    s = structures[roi]
    s['planes'] = rtss.GetStructureCoordinates(roi)
    s['thickness'] = thickness if thickness else rtss.CalculatePlaneThickness(
        s['planes'])

    calcdvh = _calculate_dvh(s, rtdose, limit, calculate_full_volume,
                             use_structure_extents, interpolation_resolution,
                             interpolation_segments_between_planes,
                             callback)
    return dvh.DVH(counts=calcdvh.histogram,
                   bins=(np.arange(0, 2) if (calcdvh.histogram.size == 1) else
                         np.arange(0, calcdvh.histogram.size + 1) / 100),
                   dvh_type='differential',
                   dose_units='Gy',
                   notes=calcdvh.notes,
                   name=s['name']).cumulative
Exemple #30
0
    def calculate_review_dvh(self):
        global x, y

        patches = {
            'x': [(0, [])],
            'y': [(0, [])],
            'roi_name': [(0, '')],
            'volume': [(0, 1)],
            'min_dose': [(0, '')],
            'mean_dose': [(0, '')],
            'max_dose': [(0, '')],
            'mrn': [(0, '')],
            'rx_dose': [(0, 1)]
        }

        try:
            if not self.sources.dvhs.data['x']:
                self.query.update_data()

            else:
                file_index = self.temp_dvh_info.mrn.index(
                    self.select_reviewed_mrn.value)
                roi_index = self.dvh_review_rois.index(
                    self.select_reviewed_dvh.value)
                structure_file = self.temp_dvh_info.structure[file_index]
                plan_file = self.temp_dvh_info.plan[file_index]
                dose_file = self.temp_dvh_info.dose[file_index]
                key = list(
                    self.temp_dvh_info.get_roi_names(
                        self.select_reviewed_mrn.value))[roi_index]

                rt_st = dicomparser.DicomParser(structure_file)
                rt_structures = rt_st.GetStructures()
                review_dvh = dvhcalc.get_dvh(structure_file, dose_file, key)
                dicompyler_plan = dicomparser.DicomParser(plan_file).GetPlan()

                roi_name = rt_structures[key]['name']
                volume = review_dvh.volume
                min_dose = review_dvh.min
                mean_dose = review_dvh.mean
                max_dose = review_dvh.max
                if not self.review_rx.value:
                    rx_dose = float(dicompyler_plan['rxdose']) / 100.
                    self.review_rx.value = str(round(rx_dose, 2))
                else:
                    rx_dose = round(float(self.review_rx.value), 2)

                x = review_dvh.bincenters
                if max(review_dvh.counts):
                    y = np.divide(review_dvh.counts, max(review_dvh.counts))
                else:
                    y = review_dvh.counts

                if self.radio_group_dose.active == 1:
                    f = 5000
                    bin_count = len(x)
                    new_bin_count = int(bin_count * f / (rx_dose * 100.))

                    x1 = np.linspace(0, bin_count, bin_count)
                    x2 = np.multiply(
                        np.linspace(0, new_bin_count, new_bin_count),
                        rx_dose * 100. / f)
                    y = np.interp(x2, x1, review_dvh.counts)
                    y = np.divide(y, np.max(y))
                    x = np.divide(np.linspace(0, new_bin_count, new_bin_count),
                                  f)

                if self.radio_group_volume.active == 0:
                    y = np.multiply(y, volume)

                patches = {
                    'x': [(0, x)],
                    'y': [(0, y)],
                    'roi_name': [(0, roi_name)],
                    'volume': [(0, volume)],
                    'min_dose': [(0, min_dose)],
                    'mean_dose': [(0, mean_dose)],
                    'max_dose': [(0, max_dose)],
                    'mrn': [(0, self.select_reviewed_mrn.value)],
                    'rx_dose': [(0, rx_dose)]
                }

        except:
            pass

        self.sources.dvhs.patch(patches)

        self.update_source_endpoint_calcs()