def RegisterCT2MRI(): text = "Runs registration of CT imaging (moving) and T1-sequence (fixed) in order to get them into same space. " \ "The default option (see cfg-file) constitutes three steps: a) Rigid, b) Affine registration and c) " \ "Symmetric image Normalisation (SyN). For details see 'SyNRA'-option at " \ "https://github.com/ANTsX/ANTsPy/blob/master/ants/registration/interface.py. Non-default means, that the " \ "command-line for running ANTsRegistration can be modified according to the file: " \ "cmdline_ANTsRegistration.txt in the .utils directory. Please make sure to include all terms within *...* " \ "into the text-file as they will be replaced." return Output.split_lines(text)
def RegisterMRI2template(): text = "Runs registration of MR-imaging (moving) to template sequences (fixed) as defined in the config file in " \ "order to get them into same space. The default option constitutes three steps: a) Rigid, " \ "b) Affine registration and c) Symmetric image Normalisation (SyN), although all options available in ANTsPy" \ "are possible (for details see https://github.com/ANTsX/ANTsPy/blob/master/ants/registration/interface.py. " \ "Non-default means, that the command-line for running ANTsRegistration can be modified according to " \ "the file: cmdline_ANTsRegistration.txt in the .utils directory. Please make sure to include all terms " \ "within *...* into the text-file as they will be replaced." return Output.split_lines(text)
def wrapper_multiprocessing(self, fileIDs, subjects, modality): """To avoid redundancy between CT and MR registration to common space, this part is wrapped up here """ working_dir = self.cfg['folders']['nifti'] prefix = '{}2template'.format(modality) # Start multiprocessing framework start_multi = time.time() status = mp.Queue() processes = [ mp.Process(target=self.ANTsCoregisterMultiprocessing, args=(filename_fixed, filename_moving, no_subj, os.path.join(working_dir, no_subj), 'registration', prefix, status)) for filename_fixed, filename_moving, no_subj in fileIDs ] for p in processes: p.start() while any([p.is_alive() for p in processes]): while not status.empty(): filename_fixed, filename_moving, no_subj = status.get() print("\tRegistering {} (f) to {} (m), in ANTsPy\n".format( filename_fixed, os.path.split(filename_moving)[1], no_subj)) time.sleep(0.1) for p in processes: p.join() # Functions creating/updating pipeline log, which documents all steps along with settings for subjID in subjects: files_processed = [(os.path.split(file_moving)[1], os.path.split(file_fixed)[1]) for file_fixed, file_moving, subj_no in fileIDs if subj_no == subjID] log_text = "{} successfully registered (@{}) to \n{}, \n\n Mean Duration per subject: {:.2f} " \ "secs".format(files_processed[0][0], time.strftime("%Y%m%d-%H%M%S"), files_processed[0][1], (time.time() - start_multi) / len(subjects)) Output.logging_routine(text=Output.split_lines(log_text), cfg=self.cfg, subject=str(subjID), module='{}-Registration'.format(modality), opt=self.cfg['preprocess']['registration'], project="") print( '\nIn total, a list of {} subject(s) was processed; {} registration took {:.2f}secs. ' 'overall'.format(len(subjects), modality, time.time() - start_multi))
def LabelFilenameDCM2NII(): text = "Please enter here the prefix/file information that should be included when saving NIFTI files. " \ "Characters such as '<', '>', ':', '/' ... MUST be avoided (default is %p_%s). " \ "From documentation:\n\t %a : antenna (coil) number inserted. For example, the output filename" \ " 'myName%a' would generate 'myName1', 'myName2', each for each coil. Note that most scans combine data " \ "from all coils and in these cases this option is ignored. For example, most scans which combine " \ "data from all coils would simply be called 'myName'\n\t%d :" \ "series description (0008,103E) inserted. For example, an echo-planar image " \ "converted with 'myName%d' would yield 'myNameEPI' \n\t%e : echo number " \ "inserted. For example, a sequence with two echo times converted with the output" \ "filename 'myName%e' will yield 'myName1' and 'myName2'. Note that most MRI" \ " sequences only use a single echo time, and in these cases you would only get " \ "'myName1'.\n\t%f : input folder name inserted. For example, the output " \ "filename'myName%f' combined with an input folder '/usr/Subj22' will result in " \ "the output file named'myNameSubj22.nii' \n\t%i : patient ID " \ "(DICOM tag 0010,0020) inserted. For example, the output filename 'myName%i' " \ " would convert an image where the patient ID is named 'ID123' to be " \ " 'myNameID123.nii' \n\t%m : manufacturer name For example, the output filename" \ " 'myName%m' would convert an image from a GE scanner to 'myNameGE.nii', while " \ " an image from Philips would be 'myNamePh.nii', whereas Siemens would be " \ " 'myNameSi.nii', otherwise the manufacturer is not available ('myNameNA.nii')." \ " (requires dcm2nii versions from 2015 or later). \n\t%n : subject name (DICOM" \ " tag 0010,0010) inserted. For example, the output filename 'myName%n' would " \ " convert an image from John Doe to 'myNameJohnDoe.nii'. This option works best" \ " if your participant names use only English letters, for other European " \ " languages you may find it makes some basic conversions ('Müller' will become " \ " 'Muller'). For non-European languages you will find this option unsatisfactory" \ ". Perhaps future versions can support DICOM tag 0008,0005. \n\t %p: protocol" \ " name (DICOM tag 0018,1030) inserted. For example, the output filename" \ " 'myName%p' would convert image where protocol is named T1 to be 'myNameT1.nii'" \ " \n\t%q: sequence name (DICOM tag 0018,1020) inserted.For example," \ " the output filename 'myName%q' would convert a Spin Echo sequence to be " \ "'myNameSE.nii' (new feature, in versions from 30Aug2015).\n\t%s : series (DICOM " \ "tag 0020,0011) inserted. For example, the output filename 'myName%s' would " \ "convert the second series to be 'myName2.nii'. If you want to zero-pad the " \ "series number, insert the number of digits desired (0..9). For example applying " \ "the filter 'm%s' when converting 11 series will create files that will cause" \ " problems for a simple alphabetical sort, e.g. 'm1.nii,m11.nii,m2.nii...m9.nii'." \ " In contrast specifying 'm%3s' will help sorting (e.g. 'm001.nii,m002.nii" \ "...m011.nii').\n\t%t : session date and time inserted (DICOM tags 0008,0021 and " \ "0008,0030). For example, the output filename 'myName%t' would convert an image " \ "where the session began at 1:23pm on 13 Jan 2014 as 'myName20140113132322.nii' " \ "\n\t%z : Sequence Name (0018,0024) inserted, so a T1 scan converted with " \ "'myName%z' might yield 'myNameT1'." return Output.split_lines(text)
def renameFolders(): text = 'Displays a small GUI which enables to change the prefix for all folders. This new prefix is saved in ' \ 'the configuration file' return Output.split_lines(text)
def ProbabilisticMask(): text = 'Defines whether or not use probablisitic mask. With the use of ANTs for registration and segmentation, ' \ ' a probabilistic mask is created via ANTsPyNet routines. if set to "yes", the 95th percentile is used, ' \ ' otherwise data is subtracted from the edges with a sphere analog to the procedure in Lead-DBS' return Output.split_lines(text)
def dcm2niix_multiprocessing(self, name_subj, no_subj, dcm2niix_bin, last_idx, total_subj, status): """function intended to provide multiprocessing approach to speed up extraction of DICOM data to nifti files""" modalities = ['CT', 'MRI'] if self.logfile: log_filename = os.path.join( ROOTDIR, 'logs', 'log_DCM2NII_' + str(no_subj + last_idx) + time.strftime("%Y%m%d-%H%M%S")) else: log_filename = os.devnull subj_outdir = os.path.join( self.outdir, self.cfg['folders']['prefix'] + str(no_subj + last_idx)) FileOperations.create_folder(subj_outdir) start_time_subject = time.time() keptfiles, deletedfiles = ([] for _ in range(2)) for mod in modalities: status.put((name_subj, mod, no_subj, total_subj)) input_folder_name = os.path.join(self.inputdir, name_subj + mod) # input_folder_files = [f.path for f in os.scandir(input_folder_name) # if (f.is_dir() and ('100' in f.path or 'DICOM' in f.path or '001' in f.path))] input_folder_files = [] [ input_folder_files.append(item) for item in os.listdir(input_folder_name) if (os.path.isdir(os.path.join(input_folder_name, item)) and ( '100' in item or 'DICOM' in item or '001' in item)) ] orig_stdout = sys.stdout sys.stdout = open(log_filename, 'w') for folder in input_folder_files: subprocess.call( [ dcm2niix_bin, '-a', 'y', # anonimisation of DICOM data '-b', self.cfg['preprocess']['dcm2nii']['BIDSsidecar'][0], '-z', self.cfg['preprocess']['dcm2nii']['OutputCompression'] [0], '-f', self.cfg['preprocess']['dcm2nii']['OutputFileStruct'], '-o', subj_outdir, '-w', str(self.cfg['preprocess']['dcm2nii'] ['NameConflicts']), '-v', str(self.cfg['preprocess']['dcm2nii']['Verbosity']), '-x', str(self.cfg['preprocess']['dcm2nii']['ReorientCrop']), folder ], stdout=sys.stdout, stderr=subprocess.STDOUT) sys.stdout.close() sys.stdout = orig_stdout files_kept, files_deleted = self.select_sequences(subj_outdir) keptfiles.extend(files_kept) deletedfiles.extend(files_deleted) # Functions creating/updating pipeline log, which document individually all steps along with settings log_text = "{} files successfully converted: {}, \n\nand {} deleted: {}.\nDuration: {:.2f} secs" \ .format(len(set(keptfiles)), '\n\t{}'.format('\n\t'.join(os.path.split(x)[1] for x in sorted(set(keptfiles)))), len(set(deletedfiles)), '\n\t{}'.format('\n\t'.join(os.path.split(x)[1] for x in sorted(set(deletedfiles)))), time.time() - start_time_subject) Output.logging_routine(text=Output.split_lines(log_text), cfg=self.cfg, subject=self.cfg['folders']['prefix'] + str(no_subj), module='dcm2nii', opt=self.cfg['preprocess']['dcm2nii'], project="")
def runDCM2NII(): text = 'Separate GUI to transform DICOM files using Chris Rordens routines ' \ 'see http://www.nitrc.org/projects/dcm2nii/' return Output.split_lines(text)
def LabelVerbosity(): text = "According to the documentation this defines whether dcm2nii should be silent (0), with numerous" \ " output (1) or logorrhoeic (2); default = 1" return Output.split_lines(text)
def LabelResampleImages(): text = "Defines the spacing to which imaging will be resampled at. If set to '0' resampling is skipped" return Output.split_lines(text)
def N4BiasCorrection(): text = "Reduces the Bias from the MRI using the N4Bias correction method as described in N.J. Tustison, ..., and " \ "J.C. Gee. "'N4ITK: Improved N3 Bias Correction IEEE Transactions on Medical Imaging, ' \ "29(6):1310-1320, June 2010." return Output.split_lines(text)
def BSplineDistance(): text = "B-spline fitting parameters. With respect to the official documentation, only sizing of the mesh " \ "elements can be used, whereas the other possible options are left as default. A float value of " \ "the distance between the knots defining the B-Spline mesh can be chosen" return Output.split_lines(text)
def displayFolderContent(): text = 'Display the data available for the subj selected, to see what is present. Note: if more than one ' \ 'subject is selected an error will be dropped!' return Output.split_lines(text)
def LabelShrink(): text = "Downsample level applied, specified as integer (factors <= 4 commonly used, see https://manpages.ubuntu.com/manpages/trusty/man1/N4BiasFieldCorrection.1.html)." return Output.split_lines(text)
def LabelPrefixBias(): text = "The prefix for the N4Bias-corrected files. Be cautious here as changing this may result in some parts of the toolbox not working" return Output.split_lines(text)
def LabelReorientCrop(): text = "This option sets whether reorientation according to header and cropping should be performed or not" return Output.split_lines(text)
def renameFoldersInput(): text = "Please enter here the string that replaces the suffix (see left). \nIt may only contain characters, " \ "numbers and the special characters: '_' and '.', but not at the end " return Output.split_lines(text)
def ResampleMethod(): text = "Defines the method appled for resampling images: \n\t - (lin) - linear\n\t - (nn) - nearest neighbour\n\t - (gauss) - gaussian \n\t - (bspline) - B-spline interpolation" return Output.split_lines(text)
def subjectDetails(): text = 'Displays the subject details and the names of the subjects corresponding to the prefix' return Output.split_lines(text)
def compareNIFTIfiles(): text = "Opens a dialog in order to select NIFTI-files which can be displayed using the image viewer. This enables" \ " to see whether there have been any problems whatsoever" return Output.split_lines(text)
def ANTsSettings(): text = "Displays a separate window in which the options for the ANTsPy routines can be modified." return Output.split_lines(text)
def saveDirButton(): text = "The folder is saved into a configuration file, which is read when GUI is loaded in order to " \ "facilitate continuing with previous session (file located in main folder of Toolbox). Only available " \ "for some parts of the code" return Output.split_lines(text)
def N4BiasConvergence(): text = "Maximum number of iterations for each shrinkage factor and convergence tolerance" return Output.split_lines(text)
def LabelBIDS(): text = "This option sets whether sidecar is stored or not (By default, data is always anonimised and BIDS " \ "are always stored). " return Output.split_lines(text)
def ChangeWdirDICOM(): text = ( 'Opens another dialog in which the DICOM directory may be changed. For actions to take place, press ' '"reload files" button') return Output.split_lines(text)
def DiffPrefix(): text = "Prefix for diffusion weighted imaging sequences (if available)." return Output.split_lines(text)
def run_dcm2niix(): text = "Before running this script, please make sure that the prefrences are set properly. moreover it is " \ "recommended to name DICOM folders xxxCT and xxxMRI or one of both. cDBS will specifically look for these " \ "folders to convert DICOM data from." return Output.split_lines(text)
def PaCER_MetalThreshold(): text = "The threshold used to scan for artifacts which may correspond to a lead within the mask of the brain. " \ "In case none is found, this is lowered automatically" return Output.split_lines(text)
def __init__(self, parent=None): super(QWidget, self).__init__(parent) # Load configuration files and general settings self.cfg = Configuration.load_config(ROOTDIR) if os.path.isdir(self.cfg['folders']['dicom']): self.dicomdir = self.cfg['folders']['dicom'] else: self.dicomdir = FileOperations.set_wdir_in_config(self.cfg, foldername='dicom', init=True) self.cfg['folders']['dicom'] = self.dicomdir self.cfg['folders']['rootdir'] = ROOTDIR Configuration.save_config(ROOTDIR, self.cfg) # Create general layout self.tot_layout = QVBoxLayout(self) self.mid_layout = QHBoxLayout(self) # ============================ Create upper of GUI, i.e. working directory ============================ self.folderboxDcm2nii = QGroupBox("Directory (DICOM-files)") self.HBoxUpperDcm2nii = QVBoxLayout(self.folderboxDcm2nii) self.label_dicomdir = QLabel('dicom DIR: {}'.format(self.dicomdir)) self.HBoxUpperDcm2nii.addWidget(self.label_dicomdir) self.btn_dicomdir = QPushButton('Change working \ndirectory') self.btn_dicomdir.setFixedSize(150, 40) self.btn_dicomdir.clicked.connect(self.change_dicomdir) self.btn_savedir = QPushButton('Save directory \nto config file') self.btn_savedir.setFixedSize(150, 40) self.btn_savedir.setToolTip(Output.split_lines(setToolTips.saveDirButton())) self.btn_savedir.clicked.connect(self.save_cfg) hlay_upper = QHBoxLayout() hlay_upper.addWidget(self.btn_dicomdir) hlay_upper.addWidget(self.btn_savedir) hlay_upper.addStretch(1) self.HBoxUpperDcm2nii.addLayout(hlay_upper) # ==================== Create Content for Lists, i.e. input/output ==================== self.listboxInputDcm2nii = QGroupBox('Available subjects in working directory') self.listboxInput = QVBoxLayout(self.listboxInputDcm2nii) self.mInput = QListWidget() self.listboxInput.addWidget(self.mInput) self.mButtonToAvailable = QPushButton("<<") self.mBtnMoveToAvailable = QPushButton(">") self.mBtnMoveToSelected = QPushButton("<") self.mButtonToSelected = QPushButton(">>") self.mBtnUp = QPushButton("Up") self.mBtnDown = QPushButton("Down") self.listboxOutputDcm2nii = QGroupBox('Subjects to process') self.listboxOutput = QVBoxLayout(self.listboxOutputDcm2nii) self.mOutput = QListWidget() self.listboxOutput.addWidget(self.mOutput) # First column on the left side vlay = QVBoxLayout() vlay.addStretch() vlay.addWidget(self.mBtnMoveToAvailable) vlay.addWidget(self.mBtnMoveToSelected) vlay.addStretch() vlay.addWidget(self.mButtonToAvailable) vlay.addWidget(self.mButtonToSelected) vlay.addStretch() # Second column on the right side vlay2 = QVBoxLayout() vlay2.addStretch() vlay2.addWidget(self.mBtnUp) vlay2.addWidget(self.mBtnDown) vlay2.addStretch() # ==================== Lower part of GUI, i.e. Preferences/Start estimation ==================== self.btn_preferences = QPushButton("Preferences") self.btn_preferences.clicked.connect(self.settings_show) self.btn_run_dcm2niix = QPushButton("Run dcm2niix") self.btn_run_dcm2niix.setToolTip(setToolTips.run_dcm2niix()) self.btn_run_dcm2niix.clicked.connect(self.start_converting) hlay_bottom = QHBoxLayout() hlay_bottom.addStretch(1) hlay_bottom.addWidget(self.btn_preferences) hlay_bottom.addWidget(self.btn_run_dcm2niix) hlay_bottom.addStretch() # ==================== Set all contents to general Layout ======================= self.mid_layout.addWidget(self.listboxInputDcm2nii) self.mid_layout.addLayout(vlay) self.mid_layout.addWidget(self.listboxOutputDcm2nii) self.mid_layout.addLayout(vlay2) self.tot_layout.addWidget(self.folderboxDcm2nii) self.tot_layout.addLayout(self.mid_layout) self.tot_layout.addLayout(hlay_bottom) try: self.mInput.clear() items = FileOperations.list_folders(inputdir=self.cfg['folders']['dicom'], prefix='', files2lookfor='') items = self.check_for_complete_input(list(items)) self.add_available_subj(items) except FileExistsError: print('{} without any valid files/folders, continuing ...'.format(self.dicomdir)) self.update_buttons_status() self.connections()
def PaCER_Lambda(): text = "Not sure what it does!! Please double check" return Output.split_lines(text)