Пример #1
0
def test_readDicom():
    dicomImg1 = imgHandler.readDicomFromFile(dicomFile)
    vol1 = imgHandler.parseDicomVolume(dicomImg1, 64)
    assert vol1 is not None

    with open(dicomFile, 'rb') as fp:
        data = fp.read()
    dicomImg2 = imgHandler.readDicomFromBuffer(data)
    vol2 = imgHandler.parseDicomVolume(dicomImg2, 64)
    assert vol2 is not None
    assert (vol1 == vol2).all()

    # if dataInterface is not initialized with allowedDirs or allowedFileTypes it should fail
    dataInterface = DataInterface()
    with pytest.raises(ValidationError):
        dataInterface.initWatch(dicomDir, '*.dcm', 0)

    # Now allow all dirs and file types
    dataInterface = DataInterface(allowedDirs=['*'], allowedFileTypes=['*'])
    dataInterface.initWatch(dicomDir, '*.dcm', 0)
    dicomImg3 = imgHandler.readRetryDicomFromDataInterface(
        dataInterface, dicomFile)
    vol3 = imgHandler.parseDicomVolume(dicomImg3, 64)
    assert vol3 is not None
    assert (vol1 == vol3).all()

    # read in a truncated file, should fail and return None.
    trucatedDicomFile = os.path.join(dicomDir, test_dicomTruncFile)
    dicomImg4 = imgHandler.readRetryDicomFromDataInterface(
        dataInterface, trucatedDicomFile)
    assert dicomImg4 is None

    # Test convert to nifti
    niftiObject = dicomreaders.mosaic_to_nii(dicomImg3)
    assert niftiObject is not None

    dataInterface.fileWatcher.__del__()
    dataInterface.fileWatcher = None

    # Test anonymization of sensitive patient fields
    def countUnanonymizedSensitiveAttrs(dicomImg):
        sensitiveAttrs = 0
        for attr in imgHandler.attributesToAnonymize:
            if hasattr(dicomImg, attr) and getattr(dicomImg, attr) != "":
                sensitiveAttrs += 1
        return sensitiveAttrs

    dicomImg5 = imgHandler.readDicomFromFile(dicomFile)
    assert countUnanonymizedSensitiveAttrs(dicomImg5) >= 1

    imgHandler.anonymizeDicom(dicomImg5)
    assert countUnanonymizedSensitiveAttrs(dicomImg5) == 0
Пример #2
0
def doRuns(cfg, dataInterface, subjInterface, webInterface):
    """
    This function is called by 'main()' below. Here, we use the 'dataInterface'
    to read in dicoms (presumably from the scanner, but here it's from a folder
    with previously collected dicom files), doing some sort of analysis in the
    cloud, and then sending the info to the web browser.

    INPUT:
        [1] cfg - configuration file with important variables)
        [2] dataInterface - this will allow this script runnin in the cloud to access 
                files from the stimulus computer, which receives dicom files directly
                from the MRI Scanner console
        [3] subjInterface - this allows sending feedback (e.g. classification results)
                to a subjectService running on the presentation computer to provide
                feedback to the subject (and optionally get their response).
        [4] webInterface - this allows updating information on the experimenter webpage.
                For example to plot data points, or update status messages.
    OUTPUT:
        None.
    """

    # variables we'll use throughout
    scanNum = cfg.scanNum[0]
    runNum = cfg.runNum[0]

    print(f"Doing run {runNum}, scan {scanNum}")

    """
    Before we get ahead of ourselves, we need to make sure that the necessary file
        types are allowed (meaning, we are able to read them in)... in this example,
        at the very least we need to have access to dicom and txt file types.
    use the function 'allowedFileTypes' in 'fileClient.py' to check this!
    If allowedTypes doesn't include the file types we need to use then the 
        file service (scannerDataService) running at the control room computer will
        need to be restarted with the correct list of allowed types provided.

    INPUT: None
    OUTPUT:
          [1] allowedFileTypes (list of allowed file types)
    """
    allowedFileTypes = dataInterface.getAllowedFileTypes()
    if verbose:
        print(""
        "-----------------------------------------------------------------------------\n"
        "Before continuing, we need to make sure that dicoms are allowed. To verify\n"
        "this, use the 'allowedFileTypes'.\n"
        "Allowed file types: %s" %allowedFileTypes)

    # obtain the path for the directory where the subject's dicoms live
    if cfg.isSynthetic:
        cfg.dicomDir = cfg.imgDir
    else:
        subj_imgDir = "{}.{}.{}".format(cfg.datestr, cfg.subjectName, cfg.subjectName)
        cfg.dicomDir = os.path.join(cfg.imgDir, subj_imgDir)
    if verbose:
        print("Location of the subject's dicoms: \n" + cfg.dicomDir + "\n"
        "-----------------------------------------------------------------------------")

    #  If a dicomNamePattern is supplied in the config file, such as
    #  "001_{SCAN:06d}_{TR:06d}.dcm", then call stringPartialFormat() to 
    #  set the SCAN number for the series of Dicoms we will be streaming.
    dicomScanNamePattern = stringPartialFormat(cfg.dicomNamePattern, 'SCAN', scanNum)

    """
    There are several ways to receive Dicom data from the control room computer:
    1. Using `initWatch()` and 'watchFile()` commands of dataInterface or the
        helper function `readRetryDicomFromDataInterface()` which calls watchFile()
        internally.
    2. Using the streaming functions with `initScannerStream()` and `getImageData(stream)`
        which are also part of the dataInterface.
    """
    if useInitWatch is True:
        """
        Initialize a watch for the entire dicom folder using the function 'initWatch'
        of the dataInterface. (Later we will use watchFile() to look for a specific dicom)
        INPUT:
            [1] cfg.dicomDir (where the subject's dicom files live)
            [2] cfg.dicomNamePattern (the naming pattern of dicom files)
            [3] cfg.minExpectedDicomSize (a check on size to make sure we don't
                    accidentally grab a dicom before it's fully acquired)
        """
        if verbose:
            print("• initalize a watch for the dicoms using 'initWatch'")
        dataInterface.initWatch(cfg.dicomDir, dicomScanNamePattern, cfg.minExpectedDicomSize)

    else:  # use Stream functions
        """
        Initialize a Dicom stream by indicating the directory and dicom file pattern that
        will be streamed.

        INPUTs to initScannerStream():
            [1] cfg.dicomDir (where the subject's dicom files live)
            [2] dicomScanNamePattern (the naming pattern of dicom files)
            [3] cfg.minExpectedDicomSize (a check on size to make sure we don't
                    accidentally grab a dicom before it's fully acquired)
        """
        streamId = dataInterface.initScannerStream(cfg.dicomDir, 
                                                dicomScanNamePattern,
                                                cfg.minExpectedDicomSize)


    """
    We will use the function plotDataPoint in webInterface whenever we
      want to send values to the web browser so that they can be plotted in the
      --Data Plots-- tab. 
    However at the start of a run we will want to clear the plot, and we can use
    clearRunPlot(runId), or clearAllPlots() also in the webInterface object.
    """
    if verbose:
        print("• clear any pre-existing plot for this run using 'clearRunPlot(runNum)'")
    webInterface.clearRunPlot(runNum)

    if verbose:
        print(""
        "-----------------------------------------------------------------------------\n"
        "In this sample project, we will retrieve the dicom file for a given TR and\n"
        "then convert the dicom file to a nifti object. **IMPORTANT: In this sample\n"
        "we won't care about the exact location of voxel data (we're only going to\n"
        "indiscriminately get the average activation value for all voxels). This\n"
        "actually isn't something you want to actually do but we'll go through the\n"
        "to get the data in the appropriate nifti format in the advanced sample\n"
        "project (amygActivation).** We are doing things in this way because it is the simplest way\n"
        "we can highlight the functionality of rt-cloud, which is the purpose of\n"
        "this sample project.\n"
        ".............................................................................\n"
        "NOTE: We will use the function readRetryDicomFromDataInterface() to retrieve\n"
        "specific dicom files from the subject's dicom folder. This function calls\n"
        "'dataInterface.watchFile' to look for the next dicom from the scanner.\n"
        "Since we're using previously collected dicom data, this functionality is\n"
        "not particularly relevant for this sample project but it is very important\n"
        "when running real-time experiments.\n"
        "-----------------------------------------------------------------------------\n")

    num_total_TRs = 10  # number of TRs to use for example 1
    if cfg.isSynthetic:
        num_total_TRs = cfg.numSynthetic
    all_avg_activations = np.zeros((num_total_TRs, 1))
    for this_TR in np.arange(num_total_TRs):
        # declare variables that are needed to use in get data requests
        timeout_file = 5 # small number because of demo, can increase for real-time
        dicomFilename = dicomScanNamePattern.format(TR=this_TR)

        if useInitWatch is True:
            """
            Use 'readRetryDicomFromDataInterface' in 'imageHandling.py' to wait for dicom
                files to be written by the scanner (uses 'watchFile' internally) and then
                reading the dicom file once it is available.
            INPUT:
                [1] dataInterface (allows a cloud script to access files from the
                    control room computer)
                [2] filename (the dicom file we're watching for and want to load)
                [3] timeout (time spent waiting for a file before timing out)
            OUTPUT:
                [1] dicomData (with class 'pydicom.dataset.FileDataset')
            """
            print(f'Processing TR {this_TR}')
            if verbose:
                print("• use 'readRetryDicomFromDataInterface' to read dicom file for",
                    "TR %d, %s" %(this_TR, dicomFilename))
            dicomData = readRetryDicomFromDataInterface(dataInterface, dicomFilename,
                timeout_file)

        else:  # use Stream functions
            """
            Use dataInterface.getImageData(streamId) to query a stream, waiting for a 
                dicom file to be written by the scanner and then reading the dicom file
                once it is available.
            INPUT:
                [1] dataInterface (allows a cloud script to access files from the
                    control room computer)
                [2] streamId - from initScannerStream() called above
                [3] TR number - the image volume number to retrieve
                [3] timeout (time spent waiting for a file before timing out)
            OUTPUT:
                [1] dicomData (with class 'pydicom.dataset.FileDataset')
            """
            print(f'Processing TR {this_TR}')
            if verbose:
                print("• use dataInterface.getImageData() to read dicom file for"
                    "TR %d, %s" %(this_TR, dicomFilename))
            dicomData = dataInterface.getImageData(streamId, int(this_TR), timeout_file)

        if dicomData is None:
            print('Error: getImageData returned None')
            return
   
        dicomData.convert_pixel_data()

        if cfg.isSynthetic:
            niftiObject = convertDicomImgToNifti(dicomData)
        else:
            # use 'dicomreaders.mosaic_to_nii' to convert the dicom data into a nifti
            #   object. additional steps need to be taken to get the nifti object in
            #   the correct orientation, but we will ignore those steps here. refer to
            #   the advanced sample project (amygActivation) for more info about that
            if verbose:
                print("| convert dicom data into a nifti object")
            niftiObject = dicomreaders.mosaic_to_nii(dicomData)

        # take the average of all the activation values
        avg_niftiData = np.mean(niftiObject.get_fdata())
        # avg_niftiData = np.round(avg_niftiData,decimals=2)
        print("| average activation value for TR %d is %f" %(this_TR, avg_niftiData))

        max_niftiData = np.amax(niftiObject.get_fdata())
        if verbose:
            print("| max activation value for TR %d is %d" %(this_TR, max_niftiData))

        """
        INPUT:
            [1] projectComm (the communication pipe)
            [2] runNum (not to be confused with the scan number)
            [3] this_TR (timepoint of interest)
            [4] value (value you want to send over to the web browser)
            ** the inputs MUST be python integers; it won't work if it's a numpy int

        here, we are clearing an already existing plot
        """

        # Now we will send the result to be used to provide feedback for the subject.
        # Using subjInterface.setResult() will send the classification result to a
        # remote subjectService that can use the value to update the display on
        # the presentation computer.
        if verbose:
            print("| send result to the presentation computer for provide subject feedback")
        subjInterface.setResult(runNum, int(this_TR), float(avg_niftiData))

        # Finally we will use use webInterface.plotDataPoint() to send the result
        # to the web browser to be plotted in the --Data Plots-- tab.
        # Each run will have its own data plot, the x-axis will the the TR vol
        # number and the y-axis will be the classification value (float).
        # IMPORTANT ** the inputs MUST be python integers or python floats;
        #   it won't work if it's a numpy int or numpy float **
        if verbose:
            print("| send result to the web, plotted in the 'Data Plots' tab")
        webInterface.plotDataPoint(runNum, int(this_TR), float(avg_niftiData))

        # save the activations value info into a vector that can be saved later
        all_avg_activations[this_TR] = avg_niftiData

    # create the full path filename of where we want to save the activation values vector.
    #   we're going to save things as .txt and .mat files
    output_textFilename = '/tmp/cloud_directory/tmp/avg_activations.txt'
    output_matFilename = os.path.join('/tmp/cloud_directory/tmp/avg_activations.mat')

    # use 'putFile' from the dataInterface to save the .txt file
    #   INPUT:
    #       [1] filename (full path!)
    #       [2] data (that you want to write into the file)
    if verbose:
        print(""
        "-----------------------------------------------------------------------------\n"
        "• save activation value as a text file to tmp folder")
    dataInterface.putFile(output_textFilename, str(all_avg_activations))

    # use sio.save mat from scipy to save the matlab file
    if verbose:
        print("• save activation value as a matlab file to tmp folder")
    sio.savemat(output_matFilename, {'value':all_avg_activations})

    if verbose:
        print(""
        "-----------------------------------------------------------------------------\n"
        "REAL-TIME EXPERIMENT COMPLETE!")

    return
Пример #3
0
def doRuns(cfg, dataInterface, subjInterface, webInterface):
    """
    This function is called by 'main()' below. Here, we use the 'dataInterface'
    to read in dicoms (presumably from the scanner, but here it's from a folder
    with previously collected dicom files), doing some sort of analysis in the
    cloud, and then sending the info to the web browser.

    INPUT:
        [1] cfg (configuration file with important variables)
        [2] dataInterface (this will allow a script from the cloud to access files
               from the stimulus computer, which receives dicom files directly
               from the Siemens console computer)
        [3] subjInterface - this allows sending feedback (e.g. classification results)
                to a subjectService running on the presentation computer to provide
                feedback to the subject (and optionally get their response).
    OUTPUT:
        None.
    """

    # variables we'll use throughout
    scanNum = cfg.scanNum[0]
    runNum = cfg.runNum[0]

    # obtain the path for the directory where the subject's dicoms live
    cfg.dicomDir = cfg.imgDir
    print("Location of the subject's dicoms: %s\n" % cfg.dicomDir)

    # Initialize a watch for the entire dicom folder using the function 'initWatch'
    # in dataInterface. (Later we will use watchFile() to look for a specific dicom)
    #   INPUT:
    #       [1] cfg.dicomDir (where the subject's dicom files live)
    #       [2] cfg.dicomNamePattern (the naming pattern of dicom files)
    #       [3] cfg.minExpectedDicomSize (a check on size to make sure we don't
    #               accidentally grab a dicom before it's fully acquired)
    dataInterface.initWatch(cfg.dicomDir, cfg.dicomNamePattern,
                            cfg.minExpectedDicomSize)

    #We will use the function plotDataPoint in webInterface whenever we
    #  want to send values to the web browser so that they can be plotted in the
    #  --Data Plots-- tab.
    #However at the start of a run we will want to clear the plot, and we can use
    #clearRunPlot(runId), or clearAllPlots() also in the webInterface object.
    print(
        "\n\nClearing any pre-existing plot for this run using 'clearRunPlot(runNum)'"
    )
    webInterface.clearRunPlot(runNum)
    print(
        ''
        '###################################################################################'
    )

    # declare the total number of TRs
    num_trainingData = cfg.numTrainingTRs
    num_total_TRs = cfg.numSynthetic

    for this_TR in np.arange(num_total_TRs):
        # declare variables that are needed to use 'readRetryDicomFromDataInterface'
        timeout_file = 5  # small number because of demo, can increase for real-time

        # use 'getDicomFileName' from 'imageHandling.py' to obtain the filename
        #   of the dicom data you want to get... which is useful considering how
        #   complicated these filenames can be!
        #   INPUT:
        #       [1] cfg (config parameters)
        #       [2] scanNum (scan number)
        #       [3] fileNum (TR number, which will reference the correct file)
        #   OUTPUT:
        #       [1] fullFileName (the filename of the dicom that should be grabbed)
        fileName = getDicomFileName(cfg, scanNum, this_TR)

        # Use 'readRetryDicomFromDataInterface' in 'imageHandling.py' to wait for dicom
        #    files to be written by the scanner (uses 'watchFile' internally) and then
        #    reading the dicom file once it is available.
        #INPUT:
        #    [1] dataInterface (allows a cloud script to access files from the
        #        control room computer)
        #    [2] filename (the dicom file we're watching for and want to load)
        #    [3] timeout (time spent waiting for a file before timing out)
        #OUTPUT:
        #    [1] dicomData (with class 'pydicom.dataset.FileDataset')
        dicomData = readRetryDicomFromDataInterface(dataInterface, fileName,
                                                    timeout_file)

        # We can use the 'convertDicomFileToNifti' function with dicomData as
        #   input to get the data in NifTi format
        base, ext = os.path.splitext(fileName)
        assert ext == '.dcm'
        niftiFilename = base + '.nii'
        convertDicomFileToNifti(fileName, niftiFilename)
        niftiObject = readNifti(niftiFilename)

        # declare various things if it's the first TR
        if this_TR == 0:
            # load the labels and mask
            labels = np.load(os.path.join(cfg.imgDir, 'labels.npy'))
            print(os.path.join(cfg.imgDir, 'labels.npy'))
            ROI_nib = nib.load(os.path.join(currPath, 'ROI_mask.nii.gz'))
            ROI_mask = np.array(ROI_nib.dataobj)
            mask_nib = nib.Nifti1Image(ROI_mask.T, affine=niftiObject.affine)
            # declare the number of TRs we will shift the data to account for the hemodynamic lag
            num_shiftTRs = 3
            # shift the labels to account for hemodynamic lag
            shifted_labels = np.concatenate(
                [np.full((num_shiftTRs, 1), np.nan), labels])
            # set up a matrix that will hold all of the preprocessed data
            preprocessed_data = np.full((num_total_TRs, int(ROI_mask.sum())),
                                        np.nan)

        # preprocess the training data by applying the mask
        preprocessed_data[this_TR, :] = np.ravel(
            apply_mask(niftiObject, mask_nib).reshape(int(ROI_mask.sum()), 1))

        ## Now we divide into one of three possible steps

        # STEP 1: collect the training data
        if this_TR < num_trainingData:
            print('Collected training data for TR %s' % this_TR)
        # STEP 2: train the SVM model
        elif this_TR == num_trainingData:
            print(
                ''
                '###################################################################################'
            )
            print(
                'Done collecting training data! \nStart training the classifier.'
            )

            # snapshot of time to keep track of how long it takes to train the classifier
            start_time = time.time()

            scaler = StandardScaler()
            X_train = preprocessed_data[num_shiftTRs:num_trainingData]
            y_train = shifted_labels[num_shiftTRs:num_trainingData].reshape(
                -1, )
            # we don't want to include rest data
            X_train_noRest = X_train[y_train != 0]
            y_train_noRest = y_train[y_train != 0]
            # and we want to zscore the bold data
            X_train_noRest_zscored = scaler.fit_transform(X_train_noRest)
            clf = svm.SVC(kernel='linear', C=0.01, class_weight='balanced')
            clf.fit(X_train_noRest_zscored, y_train_noRest)

            # print out the amount of time it took to train the classifier
            print('Classifier done training! Time it took: %.2f s' %
                  (time.time() - start_time))
            print(
                ''
                '###################################################################################'
            )
        elif this_TR > num_trainingData:
            # apply the classifier to new data to obtain prediction IF not a rest trial
            if shifted_labels[this_TR] != 0:
                prediction = clf.predict(
                    scaler.transform(preprocessed_data[this_TR, :].reshape(
                        1, -1)))
                print(
                    f'Plotting classifier prediction for TR {this_TR}: {prediction}'
                )
                webInterface.plotDataPoint(runNum, int(this_TR),
                                           float(prediction))

                # To send feedback for the subject at the presentation computer use
                #   the subjInterface as shown below. For this demo there is no
                #   presentation computer so we won't actually it.
                # subjInterface.setResult(runNum, int(this_TR), float(prediction))
            else:
                print(f'Skipping classification because it is a rest trial')

    X_test = preprocessed_data[num_trainingData + 1:]
    y_test = shifted_labels[num_trainingData + 1:num_total_TRs].reshape(-1, )
    # we don't want to include the rest data
    X_test_noRest = X_test[y_test != 0]
    y_test_noRest = y_test[y_test != 0]
    # and we want to zscore the bold data
    X_test_noRest_zscored = scaler.transform(X_test_noRest)
    accuracy_score = clf.score(X_test_noRest_zscored, y_test_noRest)
    print('Accuracy of classifier on new data: %s' % accuracy_score)
    # print(y_test_noRest)
    # print(clf.predict(X_test_noRest_zscored))

    print(
        ""
        "###################################################################################\n"
        "REAL-TIME EXPERIMENT COMPLETE!")

    return