def getIncremental(self, volIdx=-1) -> BidsIncremental: """ Get the BIDS incremental for the corresponding DICOM image indicated by the volIdx, where volIdx is equivalent to TR id. VolIdx acts similar to a file_seek pointer. If a volIdx >= 0 is supplied the volume pointer is advanced to that position. If no volIdx or a volIdx < 0 is supplied, then the next image volume after the previous position is returned and the pointer is incremented. Args: volIdx: The volume index (or TR) within the run to retrieve. Returns: BidsIncremental for the matched DICOM for the run/volume """ if volIdx >= 0: # reset the next volume to the user specified volume self.nextVol = volIdx else: # use the default next volume pass # wait for the dicom and create a bidsIncremental dcmImg = self.dataInterface.getImageData(self.dicomStreamId, self.nextVol) dicomMetadata = getDicomMetadata(dcmImg) dicomMetadata.update(self.entities) niftiImage = convertDicomImgToNifti(dcmImg) incremental = BidsIncremental(niftiImage, dicomMetadata) self.nextVol += 1 return incremental
def readLocalDicomIncremental(volIdx, entities): # read the incremental locally for test comparison dicomPath = os.path.join(test_sampleProjectDicomPath, "001_000013_{TR:06d}.dcm".format(TR=volIdx)) dicomImg = readDicomFromFile(dicomPath) dicomMetadata = getDicomMetadata(dicomImg) dicomMetadata.update(entities) niftiImg = convertDicomImgToNifti(dicomImg) localIncremental = BidsIncremental(niftiImg, dicomMetadata) return localIncremental
def test_nifti(): with tempfile.TemporaryDirectory() as tmpDir: niftiFilename = os.path.join(tmpDir, 'nifti1.nii') imgHandler.convertDicomFileToNifti(dicomFile, niftiFilename) niftiImg1 = imgHandler.readNifti(niftiFilename) dcmImg = imgHandler.readDicomFromFile(dicomFile) niftiImgFromDcm = imgHandler.convertDicomImgToNifti(dcmImg) assert niftiImg1.header == niftiImgFromDcm.header assert np.array_equal(np.array(niftiImg1.dataobj), np.array(niftiImgFromDcm.dataobj))
def dicomToBidsinc(dicomFile, requiredMetadata: {}) -> BidsIncremental: # TODO(spolcyn): Do this all in memory -- dicom2nifti is promising # Put extra metadata in sidecar JSON file # # NOTE: This is not the final version of this method. # The conversion from DICOM to BIDS-I and gathering all required metadata # can be complex, as DICOM doesn't necessarily have the metadata required # for BIDS in it by default. Thus, another component will handle the logic # and error handling surrounding this. dicomImg = readDicomFromFile(dicomFile) niftiImage = convertDicomImgToNifti(dicomImg) #logger.debug("Nifti header after conversion is: %s", niftiImage.header) publicMeta, privateMeta = getDicomMetadata(dicomImg) publicMeta.update(privateMeta) # combine metadata dictionaries publicMeta.update(requiredMetadata) return BidsIncremental(niftiImage, requiredMetadata, publicMeta)
def dicomToBidsInc(dicomImg: pydicom.dataset.Dataset, extraMetadata: dict = {}) -> BidsIncremental: # TODO(spolcyn): Do this all in memory -- dicom2nifti is promising # Currently, there are 4 disk operations: # 1) Read DICOM (by dcm2niix) # 2) Write NIfTI # 3) Read NIfTI # 4) Read DICOM (for metadata) # NOTE: This is not the final version of this method. # The conversion from DICOM to BIDS-I and gathering all required metadata # can be complex, as DICOM doesn't necessarily have the metadata required # for BIDS in it by default. Thus, another component should handle the logic # and error handling surrounding this. niftiImage = convertDicomImgToNifti(dicomImg) metadata = getDicomMetadata(dicomImg) metadata.update(extraMetadata) return BidsIncremental(image=niftiImage, imageMetadata=metadata)
def doRuns(cfg, fileInterface, projectComm): """ This function is called by 'main()' below. Here, we use the 'fileInterface' 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] fileInterface (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] projectComm (communication pipe to talk with projectInterface) OUTPUT: None. This is the main function that is called when you run 'sample.py'. Here, you will set up an important argument parser (mostly provided by the toml configuration file), initiate the class fileInterface, and then call the function 'doRuns' to actually start doing the experiment. """ # variables we'll use throughout scanNum = cfg.scanNum[0] runNum = cfg.runNum[0] # 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! # INPUT: None # OUTPUT: # [1] allowedFileTypes (list of allowed file types) allowedFileTypes = fileInterface.allowedFileTypes() 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) print( "Location of the subject's dicoms: \n%s\n" % cfg.dicomDir, "-----------------------------------------------------------------------------" ) # initialize a watch for the entire dicom folder (it doesn't look for a # specific dicom) using the function 'initWatch' in 'fileClient.py' # 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) print("• initalize a watch for the dicoms using 'initWatch'") fileInterface.initWatch(cfg.dicomDir, cfg.dicomNamePattern, cfg.minExpectedDicomSize) # we will use the function 'sendResultToWeb' in 'projectUtils.py' whenever we # want to send values to the web browser so that they can be plotted in the # --Data Plots-- tab # 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 print("• clear any pre-existing plot using 'sendResultToWeb'") projUtils.sendResultToWeb(projectComm, runNum, None, None) 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.** 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 'readRetryDicomFromFileInterface' to retrieve\n" "specific dicom files from the subject's dicom folder. This function calls\n" "'fileInterface.watchFile' to look for the next dicom from the scanner.\n" "Since we're using previously collected dicom data, this is 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 'readRetryDicomFromFileInterface' timeout_file = 5 # small number because of demo, can increase for real-time # use 'getDicomFileName' from 'readDicom.py' to obtain the filename structure # 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 'readRetryDicomFromFileInterface' in 'readDicom.py' to wait for dicom # files to come in (by using 'watchFile' in 'fileClient.py') and then # reading the dicom file once it receives it detected having received it # INPUT: # [1] fileInterface (this will allow a script from the cloud to access files # from the stimulus computer that receives dicoms from the Siemens # console computer) # [2] filename (for 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("• use 'readRetryDicomFromFileInterface' to read dicom file for", "TR %d, %s" % (this_TR, fileName)) dicomData = readRetryDicomFromFileInterface(fileInterface, fileName, timeout_file) 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' for more info about that 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_data()) # 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_data()) print("| max activation value for TR %d is %d" % (this_TR, max_niftiData)) # use 'sendResultToWeb' from 'projectUtils.py' to send the result to the # web browser to be plotted in the --Data Plots-- tab. print("| send result to the web, plotted in the 'Data Plots' tab") projUtils.sendResultToWeb(projectComm, 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 'putTextFile' from 'fileClient.py' to save the .txt file # INPUT: # [1] filename (full path!) # [2] data (that you want to write into the file) print( "" "-----------------------------------------------------------------------------\n" "• save activation value as a text file to tmp folder") fileInterface.putTextFile(output_textFilename, str(all_avg_activations)) # use sio.save mat from scipy to save the matlab file print("• save activation value as a matlab file to tmp folder") sio.savemat(output_matFilename, {'value': all_avg_activations}) print( "" "-----------------------------------------------------------------------------\n" "REAL-TIME EXPERIMENT COMPLETE!") return
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