Exemple #1
0
 def __init__(self):
     self.batch_dir = Path.cwd().joinpath('test', 'batchtestdata')
     self.dicom_structure = DICOMDirectorySearch.get_dicom_structure(
         self.batch_dir, self.DummyProgressWindow, self.DummyProgressWindow)
     self.iso_levels = self.get_iso_levels()
     self.timestamp = BatchProcessingController.create_timestamp()
     self.application = QApplication()
Exemple #2
0
def test_batch_pyrad2pyradsr(test_object):
    """
    Test that a DICOM file 'PyRadiomics-SR.dcm' is created from
    Pyrad2Pyrad-SR.
    :param test_object: test_object function, for accessing the shared
                        TestObject object.
    """
    # Loop through patient datasets
    for patient in test_object.get_patients():
        # Get the files for the patient
        cur_patient_files = \
            BatchProcessingController.get_patient_files(patient)

        # Create and setup the batch process
        process = BatchProcessPyRad2PyRadSR(test_object.DummyProgressWindow,
                                            test_object.DummyProgressWindow,
                                            cur_patient_files)

        # Start the process
        process.start()

        # Get dataset directory
        directory = process.patient_dict_container.path

        # Get Pyradiomics SR, assert it exists
        file_name = 'Pyradiomics-SR.dcm'
        path = Path(directory).joinpath(file_name)
        assert os.path.exists(str(path))

        # Delete Pyradiomics SR
        os.remove(path)
Exemple #3
0
def test_batch_pyrad2csv(test_object):
    """
    Test asserts creation of CSV as result of PyRad2CSV conversion.
    :param test_object: test_object function, for accessing the shared
                            TestObject object.
    """
    # Loop through patient datasets
    for patient in test_object.get_patients():
        cur_patient_files = BatchProcessingController.get_patient_files(
            patient)

        # Create and setup the Batch Process
        process = BatchProcessPyRad2CSV(test_object.DummyProgressWindow,
                                        test_object.DummyProgressWindow,
                                        cur_patient_files,
                                        test_object.batch_dir)

        # Target filename
        filename = 'Pyradiomics_' + test_object.timestamp + '.csv'

        # Set the filename
        process.set_filename(filename)

        # Start the process
        process.start()

        # Assert the resulting .csv file exists
        assert os.path.isfile(
            Path.joinpath(test_object.batch_dir, 'CSV', filename))
Exemple #4
0
def test_batch_dvh2csv(test_object):
    """
    Test asserts creation of CSV as result of DVH2CSV conversion.
    :param test_object: test_object function, for accessing the shared
                        TestObject object.
    """
    # Loop through patient datasets
    for patient in test_object.get_patients():
        cur_patient_files = BatchProcessingController.get_patient_files(
            patient)

        # Create and setup the Batch Process
        process = BatchProcessDVH2CSV(test_object.DummyProgressWindow,
                                      test_object.DummyProgressWindow,
                                      cur_patient_files,
                                      str(test_object.batch_dir))

        # Target filename
        filename = 'DVHs_' + test_object.timestamp + '.csv'

        # Set the filename
        process.set_filename(filename)

        # Start the process
        process.start()

        # Assert the resulting .csv file exists
        assert os.path.isfile(
            Path.joinpath(test_object.batch_dir, 'CSV', filename))

        # Assert that there is DVH data in the RT Dose
        rtdose = process.patient_dict_container.dataset['rtdose']
        assert len(rtdose.DVHSequence) > 0
Exemple #5
0
def test_batch_suv2roi(test_object):
    """
    Test that at least 1 new ROI is created from SUV2ROI.
    :param test_object: test_object function, for accessing the shared
                        TestObject object.
    """
    # Loop through patient datasets
    for patient in test_object.get_patients():
        # Get the files for the patient
        cur_patient_files = BatchProcessingController.get_patient_files(
            patient)

        # Create and setup the Batch Process
        patient_weight = 70000
        process = BatchProcessSUV2ROI(test_object.DummyProgressWindow,
                                      test_object.DummyProgressWindow,
                                      cur_patient_files, patient_weight)

        # Start the process
        result = process.start()

        if not result:
            return

        # Get rtss
        rtss = process.patient_dict_container.dataset['rtss']

        # Get ROIS from rtss
        rois = []
        for roi in rtss.StructureSetROISequence:
            rois.append(roi.ROIName)

        # Assert rtss contains new rois
        difference = set(test_object.iso_levels) - set(rois)
        assert len(difference) > 0
Exemple #6
0
def test_batch_iso2roi(test_object):
    """
    Test that at least 1 new ROI is created from ISO2ROI.
    :param test_object: test_object function, for accessing the shared
                        TestObject object.
    """
    # Loop through patient datasets
    for patient in test_object.get_patients():
        # Get the files for the patient
        cur_patient_files = BatchProcessingController.get_patient_files(
            patient)

        # Create and setup the Batch Process
        process = BatchProcessISO2ROI(test_object.DummyProgressWindow,
                                      test_object.DummyProgressWindow,
                                      cur_patient_files)
        # Start the process
        result = process.start()

        # Get rtss
        if not result:
            return

        rtss = process.patient_dict_container.dataset['rtss']

        # Get ROIS from rtss
        rois = []
        for roi in rtss.StructureSetROISequence:
            rois.append(roi.ROIName)

        # Assert rtss contains new rois
        difference = set(test_object.iso_levels) - set(rois)
        assert len(difference) > 0

        # An rtss.dcm is being created during this batch test - this is
        # because the existing RTSTRUCT in DICOM-RT-02 does not match the
        # rest of the dataset. We need to delete this, or future tests
        # will fail.
        rtss_path = test_object.batch_dir.joinpath("DICOM-RT-02", "rtss.dcm")
        os.remove(rtss_path)
Exemple #7
0
def test_batch_roi_name_to_fma_id(test_object):
    """
    Test asserts an ROI changes name and one is deleted.
    :param test_object: test_object function, for accessing the shared
                        TestObject object.
    """
    # Get RTSS file path, count number of ROIs
    rtss_path = None
    for root, dirs, files in os.walk(test_object.batch_dir, topdown=True):
        for name in files:
            try:
                ds = dcmread(os.path.join(root, name))
                if ds.SOPClassUID == '1.2.840.10008.5.1.4.1.1.481.3':
                    rtss_path = os.path.join(root, name)
                    break
            except (InvalidDicomError, FileNotFoundError):
                pass

    # Assert rtss exists
    assert rtss_path is not None
    assert os.path.exists(rtss_path)

    for patient in test_object.get_patients():
        # Get the files for the patient
        cur_patient_files = BatchProcessingController.get_patient_files(
            patient)

        # Create and setup the Batch Process
        process = BatchProcessROIName2FMAID(test_object.DummyProgressWindow,
                                            test_object.DummyProgressWindow,
                                            cur_patient_files)

        # Start the process
        status = process.start()
        assert status

        # Assert no ROIs called Lungs exists
        ds = dcmread(rtss_path)
        for roi in ds.StructureSetROISequence:
            assert roi.ROIName != 'Lungs'
Exemple #8
0
    def setup_ui(self, batch_window_instance):
        """
        Sets up the UI for the batch processing window.
        """
        # Get the appropriate stylesheet
        if platform.system() == 'Darwin':
            self.stylesheet_path = "res/stylesheet.qss"
        else:
            self.stylesheet_path = "res/stylesheet-win-linux.qss"
        self.stylesheet = open(resource_path(self.stylesheet_path)).read()

        # Create class variables
        self.file_path = "Select file path..."

        # Label font
        label_font = QtGui.QFont()
        label_font.setPixelSize(14)

        # Set the window icon
        window_icon = QtGui.QIcon()
        window_icon.addPixmap(
            QtGui.QPixmap(resource_path("res/images/icon.ico")),
            QtGui.QIcon.Normal, QtGui.QIcon.Off)

        # Set window properties
        batch_window_instance.setObjectName("BatchWindowInstance")
        batch_window_instance.setWindowIcon(window_icon)
        batch_window_instance.setWindowTitle("Batch Processing")
        batch_window_instance.resize(840, 530)

        # == Directory widgets
        # Directory label
        dir_label_text = "Select directory to perform batch processing on:"
        self.dir_info_label = QtWidgets.QLabel(dir_label_text)
        self.dir_info_label.setFont(label_font)

        # Directory line edit
        self.directory_input = QtWidgets.QLineEdit()
        self.directory_input.setText(self.file_path)
        self.directory_input.textChanged.connect(self.line_edit_changed)
        self.directory_input.setStyleSheet(self.stylesheet)

        # Label to display file search status
        self.search_progress_label = QtWidgets.QLabel("No directory is "
                                                      "currently selected.")
        self.search_progress_label.setFont(label_font)

        # Browse button
        self.browse_button = QtWidgets.QPushButton("Change")
        self.browse_button.setObjectName("NormalButton")
        self.browse_button.setStyleSheet(self.stylesheet)

        # == Tab widgets
        # Tab widget
        self.tab_widget = CheckableTabWidget()
        self.tab_widget.tabBar().setObjectName("batch-tabs")
        self.tab_widget.setStyleSheet(self.stylesheet)

        # Tabs
        self.iso2roi_tab = ISO2ROIOptions()
        self.suv2roi_tab = SUV2ROIOptions()
        self.dvh2csv_tab = DVH2CSVOptions()
        self.pyrad2csv_tab = PyRad2CSVOptions()
        self.pyrad2pyradSR_tab = Pyrad2PyradSROptions()
        self.csv2clinicaldatasr_tab = CSV2ClinicalDataSROptions()
        self.clinicaldatasr2csv_tab = ClinicalDataSR2CSVOptions()
        self.batchnamecleaning_tab = ROINameCleaningOptions()
        self.batchname2fma_tab = ROIName2FMAIDOptions()

        # Add tabs to tab widget
        self.tab_widget.addTab(self.iso2roi_tab, "ISO2ROI")
        self.tab_widget.addTab(self.suv2roi_tab, "SUV2ROI")
        self.tab_widget.addTab(self.dvh2csv_tab, "DVH2CSV")
        self.tab_widget.addTab(self.pyrad2csv_tab, "PyRad2CSV")
        self.tab_widget.addTab(self.pyrad2pyradSR_tab, "Pyrad2Pyrad-SR")
        self.tab_widget.addTab(self.csv2clinicaldatasr_tab,
                               "CSV2ClinicalData-SR")
        self.tab_widget.addTab(self.clinicaldatasr2csv_tab,
                               "ClinicalData-SR2CSV")
        self.tab_widget.addTab(self.batchnamecleaning_tab, "ROI Name Cleaning")
        self.tab_widget.addTab(self.batchname2fma_tab, "ROI Name to FMA ID")

        # == Bottom widgets
        # Info text
        info_text = "Batch Processing will be performed on datasets in the "
        info_text += "selected directory."
        self.info_label = QtWidgets.QLabel(info_text)
        self.info_label.setFont(label_font)

        # Back button
        self.back_button = QtWidgets.QPushButton("Exit")
        self.back_button.setObjectName("BatchExitButton")
        self.back_button.setMaximumWidth(80)
        self.back_button.setStyleSheet(self.stylesheet)
        self.back_button.setProperty("QPushButtonClass", "fail-button")

        # Begin button
        self.begin_button = QtWidgets.QPushButton("Begin")
        self.begin_button.setObjectName("BeginButton")
        self.begin_button.setMaximumWidth(100)
        self.begin_button.setStyleSheet(self.stylesheet)
        self.begin_button.setProperty("QPushButtonClass", "success-button")
        self.begin_button.setEnabled(False)

        # == Set layout
        # Create layouts
        self.layout = QtWidgets.QVBoxLayout()
        self.directory_layout = QtWidgets.QGridLayout()
        self.middle_layout = QtWidgets.QVBoxLayout()
        self.bottom_layout = QtWidgets.QGridLayout()

        # Add top text
        self.layout.addWidget(self.dir_info_label)

        # Add directory widgets
        self.directory_layout.addWidget(self.directory_input)
        self.directory_layout.addWidget(self.browse_button, 0, 1)
        self.directory_layout.addWidget(self.search_progress_label, 1, 0)
        self.layout.addLayout(self.directory_layout)

        # Add middle widgets (patient count, tabs)
        self.middle_layout.addWidget(self.tab_widget)
        self.layout.addLayout(self.middle_layout)

        # Add bottom widgets (buttons)
        self.bottom_layout.addWidget(self.info_label, 0, 0, 2, 4)
        self.bottom_layout.addWidget(self.back_button, 2, 2, 1, 1)
        self.bottom_layout.addWidget(self.begin_button, 2, 3, 1, 1)
        self.layout.addLayout(self.bottom_layout)

        # Connect buttons to functions
        self.browse_button.clicked.connect(self.show_file_browser)
        self.begin_button.clicked.connect(self.confirm_button_clicked)
        self.back_button.clicked.connect(
            lambda: QtCore.QCoreApplication.exit(0))

        # Set window layout
        batch_window_instance.setLayout(self.layout)

        # Create batch processing controller, enable the processing
        self.batch_processing_controller = BatchProcessingController()
Exemple #9
0
class UIBatchProcessingWindow(object):
    """
    This class contains the user interface for the batch processing
    window.
    """
    def setup_ui(self, batch_window_instance):
        """
        Sets up the UI for the batch processing window.
        """
        # Get the appropriate stylesheet
        if platform.system() == 'Darwin':
            self.stylesheet_path = "res/stylesheet.qss"
        else:
            self.stylesheet_path = "res/stylesheet-win-linux.qss"
        self.stylesheet = open(resource_path(self.stylesheet_path)).read()

        # Create class variables
        self.file_path = "Select file path..."

        # Label font
        label_font = QtGui.QFont()
        label_font.setPixelSize(14)

        # Set the window icon
        window_icon = QtGui.QIcon()
        window_icon.addPixmap(
            QtGui.QPixmap(resource_path("res/images/icon.ico")),
            QtGui.QIcon.Normal, QtGui.QIcon.Off)

        # Set window properties
        batch_window_instance.setObjectName("BatchWindowInstance")
        batch_window_instance.setWindowIcon(window_icon)
        batch_window_instance.setWindowTitle("Batch Processing")
        batch_window_instance.resize(840, 530)

        # == Directory widgets
        # Directory label
        dir_label_text = "Select directory to perform batch processing on:"
        self.dir_info_label = QtWidgets.QLabel(dir_label_text)
        self.dir_info_label.setFont(label_font)

        # Directory line edit
        self.directory_input = QtWidgets.QLineEdit()
        self.directory_input.setText(self.file_path)
        self.directory_input.textChanged.connect(self.line_edit_changed)
        self.directory_input.setStyleSheet(self.stylesheet)

        # Label to display file search status
        self.search_progress_label = QtWidgets.QLabel("No directory is "
                                                      "currently selected.")
        self.search_progress_label.setFont(label_font)

        # Browse button
        self.browse_button = QtWidgets.QPushButton("Change")
        self.browse_button.setObjectName("NormalButton")
        self.browse_button.setStyleSheet(self.stylesheet)

        # == Tab widgets
        # Tab widget
        self.tab_widget = CheckableTabWidget()
        self.tab_widget.tabBar().setObjectName("batch-tabs")
        self.tab_widget.setStyleSheet(self.stylesheet)

        # Tabs
        self.iso2roi_tab = ISO2ROIOptions()
        self.suv2roi_tab = SUV2ROIOptions()
        self.dvh2csv_tab = DVH2CSVOptions()
        self.pyrad2csv_tab = PyRad2CSVOptions()
        self.pyrad2pyradSR_tab = Pyrad2PyradSROptions()
        self.csv2clinicaldatasr_tab = CSV2ClinicalDataSROptions()
        self.clinicaldatasr2csv_tab = ClinicalDataSR2CSVOptions()
        self.batchnamecleaning_tab = ROINameCleaningOptions()
        self.batchname2fma_tab = ROIName2FMAIDOptions()

        # Add tabs to tab widget
        self.tab_widget.addTab(self.iso2roi_tab, "ISO2ROI")
        self.tab_widget.addTab(self.suv2roi_tab, "SUV2ROI")
        self.tab_widget.addTab(self.dvh2csv_tab, "DVH2CSV")
        self.tab_widget.addTab(self.pyrad2csv_tab, "PyRad2CSV")
        self.tab_widget.addTab(self.pyrad2pyradSR_tab, "Pyrad2Pyrad-SR")
        self.tab_widget.addTab(self.csv2clinicaldatasr_tab,
                               "CSV2ClinicalData-SR")
        self.tab_widget.addTab(self.clinicaldatasr2csv_tab,
                               "ClinicalData-SR2CSV")
        self.tab_widget.addTab(self.batchnamecleaning_tab, "ROI Name Cleaning")
        self.tab_widget.addTab(self.batchname2fma_tab, "ROI Name to FMA ID")

        # == Bottom widgets
        # Info text
        info_text = "Batch Processing will be performed on datasets in the "
        info_text += "selected directory."
        self.info_label = QtWidgets.QLabel(info_text)
        self.info_label.setFont(label_font)

        # Back button
        self.back_button = QtWidgets.QPushButton("Exit")
        self.back_button.setObjectName("BatchExitButton")
        self.back_button.setMaximumWidth(80)
        self.back_button.setStyleSheet(self.stylesheet)
        self.back_button.setProperty("QPushButtonClass", "fail-button")

        # Begin button
        self.begin_button = QtWidgets.QPushButton("Begin")
        self.begin_button.setObjectName("BeginButton")
        self.begin_button.setMaximumWidth(100)
        self.begin_button.setStyleSheet(self.stylesheet)
        self.begin_button.setProperty("QPushButtonClass", "success-button")
        self.begin_button.setEnabled(False)

        # == Set layout
        # Create layouts
        self.layout = QtWidgets.QVBoxLayout()
        self.directory_layout = QtWidgets.QGridLayout()
        self.middle_layout = QtWidgets.QVBoxLayout()
        self.bottom_layout = QtWidgets.QGridLayout()

        # Add top text
        self.layout.addWidget(self.dir_info_label)

        # Add directory widgets
        self.directory_layout.addWidget(self.directory_input)
        self.directory_layout.addWidget(self.browse_button, 0, 1)
        self.directory_layout.addWidget(self.search_progress_label, 1, 0)
        self.layout.addLayout(self.directory_layout)

        # Add middle widgets (patient count, tabs)
        self.middle_layout.addWidget(self.tab_widget)
        self.layout.addLayout(self.middle_layout)

        # Add bottom widgets (buttons)
        self.bottom_layout.addWidget(self.info_label, 0, 0, 2, 4)
        self.bottom_layout.addWidget(self.back_button, 2, 2, 1, 1)
        self.bottom_layout.addWidget(self.begin_button, 2, 3, 1, 1)
        self.layout.addLayout(self.bottom_layout)

        # Connect buttons to functions
        self.browse_button.clicked.connect(self.show_file_browser)
        self.begin_button.clicked.connect(self.confirm_button_clicked)
        self.back_button.clicked.connect(
            lambda: QtCore.QCoreApplication.exit(0))

        # Set window layout
        batch_window_instance.setLayout(self.layout)

        # Create batch processing controller, enable the processing
        self.batch_processing_controller = BatchProcessingController()

    def show_file_browser(self):
        """
        Show the file browser for selecting a folder for the Onko
        default directory.
        """
        # Open a file dialog and return chosen directory
        folder = \
            QtWidgets.QFileDialog.getExistingDirectory(None,
                                                       'Choose Directory ..',
                                                       '')

        # If chosen directory is nothing (user clicked cancel) set to
        # user home
        if folder == "":
            folder = expanduser("~")

        # Update file path
        self.file_path = folder

        # Update directory text
        self.directory_input.setText(self.file_path)

    def line_edit_changed(self):
        """
        When the line edit box is changed, update related fields,
        start searching the directory.
        """
        self.file_path = self.directory_input.text()

        self.dvh2csv_tab.set_dvh_output_location(self.file_path, False)
        self.pyrad2csv_tab.set_pyrad_output_location(self.file_path, False)

        self.begin_button.setEnabled(False)

        self.batch_processing_controller.set_dicom_structure(None)

        self.batch_processing_controller.load_patient_files(
            self.file_path, self.search_progress, self.search_completed)

    def search_progress(self, progress_update):
        """
        Changed the progress label
        :param progress_update: current progress of file search
        """
        self.search_progress_label.setText("Loading selected directory... ( "
                                           "%s files searched)" %
                                           progress_update)

    def search_completed(self, dicom_structure):
        """
        Called when patient files are loaded
        :param dicom_structure: DICOMStructure
        """
        if dicom_structure:
            self.batch_processing_controller.set_dicom_structure(
                dicom_structure)
            self.begin_button.setEnabled(True)
            self.search_progress_label.setText("%s patients found." %
                                               len(dicom_structure.patients))

            # Update tables
            self.suv2roi_tab.populate_table(dicom_structure)

            # Update the batch name cleaning table
            batch_directory = self.directory_input.text()
            self.batchnamecleaning_tab.populate_table(dicom_structure,
                                                      batch_directory)
        else:
            self.search_progress_label.setText("No patients were found.")
            self.batch_processing_controller.set_dicom_structure(None)

    def confirm_button_clicked(self):
        """
        Executes when the confirm button is clicked.
        """
        processes = [
            'iso2roi', 'suv2roi', 'dvh2csv', 'pyrad2csv', 'pyrad2pyrad-sr',
            'csv2clinicaldata-sr', 'clinicaldata-sr2csv', 'roinamecleaning',
            'roiname2fmaid'
        ]
        selected_processes = []
        suv2roi_weights = self.suv2roi_tab.get_patient_weights()

        # Return if SUV2ROI weights is None. Alert user weights are incorrect.
        if suv2roi_weights is None:
            self.show_invalid_weight_dialog()
            return

        # Get the selected processes
        for i in range(self.tab_widget.count()):
            if self.tab_widget.isChecked(i):
                selected_processes.append(processes[i])

        # Save the changed settings
        self.iso2roi_tab.save_isodoses()

        file_directories = {
            "batch_path":
            self.file_path,
            "dvh_output_path":
            self.dvh2csv_tab.get_dvh_output_location(),
            "pyrad_output_path":
            self.pyrad2csv_tab.get_pyrad_output_location(),
            'clinical_data_input_path':
            self.csv2clinicaldatasr_tab.get_csv_input_location(),
            'clinical_data_output_path':
            self.clinicaldatasr2csv_tab.get_csv_output_location()
        }

        # Setup the batch processing controller
        self.batch_processing_controller.set_file_paths(file_directories)
        self.batch_processing_controller.set_processes(selected_processes)
        self.batch_processing_controller.set_suv2roi_weights(suv2roi_weights)

        # Set batch ROI name cleaning options if selected
        if 'roinamecleaning' in selected_processes:
            # Get ROIs, datasets, options
            name_cleaning_options = {}
            roi_name_table = self.batchnamecleaning_tab.table_roi
            for i in range(roi_name_table.rowCount()):
                # Get current ROI name and what to do with it
                roi_name = roi_name_table.item(i, 0).text()
                option = roi_name_table.cellWidget(i, 1).currentIndex()

                # Get new name text
                if isinstance(roi_name_table.cellWidget(i, 2),
                              ROINameCleaningPrefixEntryField):
                    new_name = roi_name[0:3] \
                               + roi_name_table.cellWidget(i, 2).text()
                    # Remove any whitespace, replace with underscores
                    new_name = '_'.join(new_name.split())
                else:
                    new_name = roi_name_table.cellWidget(i, 2).currentText()

                # Get the dataset(s) the ROI is in
                dataset_list = []
                dataset_combo_box = roi_name_table.cellWidget(i, 3)
                rtss_path = self.directory_input.text()
                for index in range(dataset_combo_box.count()):
                    dataset_list.append(rtss_path +
                                        dataset_combo_box.itemText(index))

                if roi_name not in name_cleaning_options.keys():
                    name_cleaning_options[roi_name] = []

                for item in dataset_list:
                    name_cleaning_options[roi_name].append(
                        [option, new_name, item])

            # Set batch name cleaning parameters in the batch processing
            # controller.
            self.batch_processing_controller.set_name_cleaning_options(
                name_cleaning_options)

        # Enable processing
        self.batch_processing_controller.start_processing()

    def show_invalid_weight_dialog(self):
        """
        Shows a dialog informing the user that an entered weight in the
        SUV2ROI tab is invalid (either negative or not a number).
        """
        button_reply = \
            QtWidgets.QMessageBox(QtWidgets.QMessageBox.Icon.Warning,
                                  "Invalid Patient Weight",
                                  "Please enter a valid patient weight.",
                                  QtWidgets.QMessageBox.StandardButton.Ok,
                                  self)
        button_reply.button(
            QtWidgets.QMessageBox.StandardButton.Ok).setStyleSheet(
                self.stylesheet)
        button_reply.exec_()
Exemple #10
0
def test_batch_clinicaldatasr2csv(test_object):
    """
    Test asserts a CSV is created with dummy clinical data. Test deletes
    CSV after running.
    :param test_object: test_object function, for accessing the shared
                        TestObject object.
    """
    # Get patient ID for dummy SR
    patient_id = None
    for root, dirs, files in os.walk(test_object.batch_dir, topdown=True):
        if patient_id is not None:
            break
        for name in files:
            try:
                ds = dcmread(os.path.join(root, name))
                if hasattr(ds, 'PatientID') \
                        and ds.SOPClassUID == '1.2.840.10008.5.1.4.1.1.2':
                    patient_id = ds.PatientID
                    break
            except (InvalidDicomError, FileNotFoundError):
                pass

    # Assert a patient ID was found
    assert patient_id is not None

    # Create dummy SR
    dcm_path = test_object.batch_dir.joinpath("Clinical-Data-SR.dcm")
    text_data = "MD5Hash: " + patient_id + "\n"
    text_data += "Age: 20\nNationality: Australian\n"
    dicom_sr = DICOMStructuredReport.generate_dicom_sr(dcm_path, ds, text_data,
                                                       "CLINICAL-DATA")
    dicom_sr.save_as(dcm_path)

    # Assert dummy SR was created
    assert os.path.exists(dcm_path)

    # Reload patient (to get newly created SR)
    patients = DICOMDirectorySearch.get_dicom_structure(
        test_object.batch_dir, test_object.DummyProgressWindow,
        test_object.DummyProgressWindow)

    # Loop through each patient
    for patient in patients.patients.values():
        # Get current patient files
        cur_patient_files = BatchProcessingController.get_patient_files(
            patient)

        # Create Batch Clinical Data SR to CSV object
        process = \
            BatchProcessClinicalDataSR2CSV(test_object.DummyProgressWindow,
                                           test_object.DummyProgressWindow,
                                           cur_patient_files,
                                           test_object.batch_dir)

        # Start the process, assert it finished successfully
        status = process.start()
        assert status

    # Assert CSV exists
    csv_path = test_object.batch_dir.joinpath("ClinicalData.csv")
    assert os.path.exists(csv_path)

    # Assert data is correct in CSV
    with open(csv_path, newline="") as stream:
        data = list(csv.reader(stream))
        stream.close()

    assert data[0] == ["MD5Hash", "Age", "Nationality"]
    assert data[1] == [patient_id, "20", "Australian"]

    # Delete dummy CSV and SR
    os.remove(csv_path)
    os.remove(dcm_path)
Exemple #11
0
def test_batch_csv2clinicaldatasr(test_object):
    """
    Test asserts an SR is created with dummy clinical data. Test deletes
    SR after running.
    :param test_object: test_object function, for accessing the shared
                        TestObject object.
    """
    # Get patient ID for dummy CSV
    patient_id = None
    for root, dirs, files in os.walk(test_object.batch_dir, topdown=True):
        if patient_id is not None:
            break
        for name in files:
            try:
                ds = dcmread(os.path.join(root, name))
                if hasattr(ds, 'PatientID'):
                    patient_id = ds.PatientID
                    break
            except (InvalidDicomError, FileNotFoundError):
                pass

    assert patient_id is not None

    # Create dummy CSV
    csv_path = test_object.batch_dir.joinpath("dummy_cd.csv")
    data = [['MD5Hash', 'Age', 'Nationality'],
            [patient_id, '20', 'Australian']]

    with open(csv_path, "w", newline="") as f:
        writer = csv.writer(f)
        writer.writerow(data[0])
        writer.writerow(data[1])
        f.close()

    # Loop through each patient
    for patient in test_object.get_patients():
        # Get current patient files
        cur_patient_files = BatchProcessingController.get_patient_files(
            patient)

        # Create Batch CSV to Clinical Data SR object
        process = \
            BatchProcessCSV2ClinicalDataSR(test_object.DummyProgressWindow,
                                           test_object.DummyProgressWindow,
                                           cur_patient_files,
                                           csv_path)

        # Start the process, assert it finished successfully
        status = process.start()
        assert status

    # Assert SR exists
    sr_path = test_object.batch_dir.joinpath("DICOM-RT-02",
                                             "Clinical-Data-SR.dcm")
    assert os.path.exists(sr_path)

    # Assert data is correct in SR
    text_data = "MD5Hash: " + patient_id + "\n"
    text_data += "Age: 20\nNationality: Australian\n"
    sr_ds = dcmread(sr_path)
    assert sr_ds.SeriesDescription == "CLINICAL-DATA"
    sr_text_data = sr_ds.ContentSequence[0].TextValue
    assert text_data == sr_text_data

    # Delete dummy CSV and SR
    os.remove(csv_path)
    os.remove(sr_path)