Example #1
0
    def __intraregister__(self, volumes: List[MedicalVolume]):
        """
        Intraregister different subvolumes to ensure they are in the same space during computation

        :param volumes: a list of MedicalVolumes
        :return:
        """
        if (not volumes) or (type(volumes) is not list) or (len(volumes) != __EXPECTED_NUM_ECHO_TIMES__):
            raise TypeError('volumes must be of type List[MedicalVolume]')

        num_echos = len(volumes)

        print('')
        print('==' * 40)
        print('Intraregistering...')
        print('==' * 40)

        # temporarily save subvolumes as nifti file
        raw_volumes_base_path = io_utils.check_dir(os.path.join(self.temp_path, 'raw'))

        # Use first subvolume as a basis for registration - save in nifti format to use with elastix/transformix
        volume_files = []
        for echo_index in range(num_echos):
            filepath = os.path.join(raw_volumes_base_path, '%03d' % echo_index + '.nii.gz')
            volume_files.append(filepath)

            volumes[echo_index].save_volume(filepath, data_format=ImageDataFormat.nifti)

        target_echo_index = 0
        target_image_filepath = volume_files[target_echo_index]

        nr = NiftiReader()
        intraregistered_volumes = [deepcopy(volumes[target_echo_index])]
        for echo_index in range(1, num_echos):
            moving_image = volume_files[echo_index]

            reg = Registration()
            reg.inputs.fixed_image = target_image_filepath
            reg.inputs.moving_image = moving_image
            reg.inputs.output_path = io_utils.check_dir(os.path.join(self.temp_path,
                                                                     'intraregistered',
                                                                     '%03d' % echo_index))
            reg.inputs.parameters = [fc.ELASTIX_AFFINE_PARAMS_FILE]
            reg.terminal_output = fc.NIPYPE_LOGGING
            print('Registering %s --> %s' % (str(echo_index), str(target_echo_index)))
            tmp = reg.run()

            warped_file = tmp.outputs.warped_file
            intrareg_vol = nr.load(warped_file)

            # copy affine from original volume, because nifti changes loading accuracy
            intrareg_vol = MedicalVolume(volume=intrareg_vol.volume,
                                         affine=volumes[echo_index].affine,
                                         headers=deepcopy(volumes[echo_index].headers))

            intraregistered_volumes.append(intrareg_vol)

        self.raw_volumes = deepcopy(volumes)
        self.volumes = intraregistered_volumes
Example #2
0
    def __apply_transform__(self, image_info, transformation_files, temp_path):
        """Apply transform(s) to moving image using transformix
        :param image_info: tuple of filepath, echo index (eg. 'scans/000.nii.gz, 0)
        :param transformation_files: list of filepaths to elastix transformations
        :param temp_path: path to store temporary data
        :return: filepath to warped file in nifti (.nii.gz) format
        """
        filename, image_id = image_info
        print('Applying transform %s' % filename)
        warped_file = ''
        for f in transformation_files:
            reg = ApplyWarp()
            reg.inputs.moving_image = filename if len(
                warped_file) == 0 else warped_file

            reg.inputs.transform_file = f
            reg.inputs.output_path = io_utils.check_dir(
                os.path.join(temp_path, '%03d' % image_id))
            reg.terminal_output = fc.NIPYPE_LOGGING
            reg_output = reg.run()

            warped_file = reg_output.outputs.warped_file
            assert warped_file != ''

        return warped_file
Example #3
0
def handle_mat(vargin):
    mat_filepath = vargin[FILE_KEY][0]
    var_name = vargin[VARIABLE_KEY][0]
    sample_dicom_path = vargin[SAMPLE_DICOM_KEY][0]
    save_path = vargin[SAVE_KEY]

    # extract array from mat file
    mat_contents = sio.loadmat(mat_filepath)
    arr = mat_contents[var_name]

    # extract pixel spacing from dicom
    pixel_spacing = dicom_utils.get_pixel_spacing(sample_dicom_path)

    v = MedicalVolume(arr, pixel_spacing)

    fs = os.path.splitext(os.path.basename(mat_filepath))
    filename = fs[0]

    if save_path is None:
        save_path = os.path.dirname(mat_filepath)

    save_path = io_utils.check_dir(save_path)

    save_filepath = os.path.join(save_path, '%s.nii.gz' % filename)

    v.save_volume(save_filepath)
Example #4
0
    def __save_quant_data__(self, dirpath):
        """Save quantitative data and 2D visualizations of femoral cartilage

        Check which quantitative values (T2, T1rho, etc) are defined for femoral cartilage and analyze these
        1. Save 2D total, superficial, and deep visualization maps
        2. Save {'medial', 'lateral'}, {'anterior', 'central', 'posterior'}, {'deep', 'superficial'} data to excel
                file

        :param dirpath: base filepath to save data
        """
        q_names = []
        dfs = []

        for quant_val in QuantitativeValues:
            if quant_val.name not in self.quant_vals.keys():
                continue

            q_names.append(quant_val.name)
            q_val = self.quant_vals[quant_val.name]
            dfs.append(q_val[1])

            q_name_dirpath = io_utils.check_dir(os.path.join(dirpath, quant_val.name.lower()))
            for q_map_data in q_val[0]:
                filepath = os.path.join(q_name_dirpath, q_map_data['filename'])
                xlabel = 'Slice'
                ylabel = 'Angle (binned)'
                title = q_map_data['title']
                data_map = q_map_data['data']

                plt.clf()

                upper_bound = BOUNDS[quant_val]
                is_picture_written = False

                if defaults.VISUALIZATION_HARD_BOUNDS:
                    plt.imshow(data_map, cmap='jet', vmin=0.0, vmax=BOUNDS[quant_val])
                    is_picture_written = True

                if defaults.VISUALIZATION_SOFT_BOUNDS and not is_picture_written:
                    if np.sum(data_map <= upper_bound) == 0:
                        plt.imshow(data_map, cmap='jet', vmin=0.0, vmax=BOUNDS[quant_val])
                        is_picture_written = True
                    else:
                        warnings.warn('%s: Pixel value exceeded upper bound (%0.1f). Using normalized scale.'
                                      % (quant_val.name, upper_bound))

                if not is_picture_written:
                    plt.imshow(data_map, cmap='jet')

                plt.xlabel(xlabel)
                plt.ylabel(ylabel)
                plt.title(title)
                plt.colorbar()

                plt.savefig(filepath)

        if len(dfs) > 0:
            io_utils.save_tables(os.path.join(dirpath, 'data.xlsx'), dfs, q_names)
Example #5
0
    def __interregister_base_file__(
        self,
        base_image_info: tuple,
        target_path: str,
        temp_path: str,
        mask_path: str = None,
        parameter_files=(fc.ELASTIX_RIGID_PARAMS_FILE,
                         fc.ELASTIX_AFFINE_PARAMS_FILE)):
        """Interregister the base moving image to the target image

        :param base_image_info: tuple of filepath, echo index (eg. 'scans/000.nii.gz, 0)
        :param target_path: filepath to target scan - should be in nifti (.nii.gz) format
        :param temp_path: path to store temporary data
        :param mask_path: path to mask to use to use as focus points for registration, mask must be binary
                            recommend that this is the path to a dilated version of the mask for registration purposes
        :param parameter_files: list of filepaths to elastix parameter files to use for transformations
        :return: tuple of the path to the transformed moving image and a list of filepaths to elastix transformations
                    (e.g. '/result.nii.gz', ['/tranformation0.txt', '/transformation1.txt'])
        """
        base_image_path, base_time_id = base_image_info
        # Register base image to the target image
        print('Registering %s (base image)' % base_image_path)
        transformation_files = []

        use_mask_arr = [False, True]
        reg_output = None
        moving_image = base_image_path

        for i in range(len(parameter_files)):
            use_mask = use_mask_arr[i]
            pfile = parameter_files[i]

            reg = Registration()
            reg.inputs.fixed_image = target_path
            reg.inputs.moving_image = moving_image
            reg.inputs.output_path = io_utils.check_dir(
                os.path.join(temp_path, '%03d_param%i' % (base_time_id, i)))
            reg.inputs.parameters = pfile

            if use_mask and mask_path is not None:
                fixed_mask_filepath = self.__dilate_mask__(
                    mask_path, temp_path)
                reg.inputs.fixed_mask = fixed_mask_filepath

            reg.terminal_output = fc.NIPYPE_LOGGING

            reg_output = reg.run()
            reg_output = reg_output.outputs
            assert reg_output is not None

            # update moving image to output
            moving_image = reg_output.warped_file

            transformation_files.append(reg_output.transform[0])

        return reg_output.warped_file, transformation_files
Example #6
0
    def save(self, im: MedicalVolume, filepath: str):
        """
        Save a MedicalVolume in NIfTI format
        :param im: a Medical Volume
        :param filepath: a string defining filepath to save image to

        :raises ValueError if filepath does not contain supported NIfTI extension
        """
        if not self.data_format_code.is_filetype(filepath):
            raise ValueError(
                '%s must be a file with extension `.nii` or `.nii.gz`' %
                filepath)

        # Create dir if does not exist
        io_utils.check_dir(os.path.dirname(filepath))

        nib_affine = im.affine
        np_im = im.volume
        nib_img = nib.Nifti1Image(np_im, nib_affine)

        nib.save(nib_img, filepath)
Example #7
0
    def interregister(self, target_path: str, target_mask_path: str = None):
        base_spin_lock_time, base_image = self.intraregistered_data['BASE']
        files = self.intraregistered_data['FILES']

        temp_interregistered_dirpath = io_utils.check_dir(
            os.path.join(self.temp_path, 'interregistered'))

        print('')
        print('==' * 40)
        print('Interregistering...')
        print('Target: %s' % target_path)
        if target_mask_path:
            print('Mask: %s' % target_mask_path)
        print('==' * 40)

        if not target_mask_path:
            parameter_files = [
                fc.ELASTIX_RIGID_PARAMS_FILE, fc.ELASTIX_AFFINE_PARAMS_FILE
            ]
        else:
            parameter_files = [
                fc.ELASTIX_RIGID_INTERREGISTER_PARAMS_FILE,
                fc.ELASTIX_AFFINE_INTERREGISTER_PARAMS_FILE
            ]

        warped_file, transformation_files = self.__interregister_base_file__(
            (base_image, base_spin_lock_time),
            target_path,
            temp_interregistered_dirpath,
            mask_path=target_mask_path,
            parameter_files=parameter_files)
        warped_files = [(base_spin_lock_time, warped_file)]

        nifti_reader = NiftiReader()

        # Load the transformation file. Apply same transform to the remaining images
        for spin_lock_time, filename in files:
            warped_file = self.__apply_transform__(
                (filename, spin_lock_time), transformation_files,
                temp_interregistered_dirpath)
            # append the last warped file - this has all the transforms applied
            warped_files.append((spin_lock_time, warped_file))

        # copy each of the interregistered warped files to their own output
        subvolumes = dict()
        for spin_lock_time, warped_file in warped_files:
            subvolumes[spin_lock_time] = nifti_reader.load(warped_file)

        self.subvolumes = subvolumes
Example #8
0
    def save(self, im, filepath):
        """
        Save a medical volume in dicom format
        :param im: a Medical Volume
        :param filepath: a path to a directory to store dicom files

        :raises ValueError if im (MedicalVolume) does not have initialized headers
        :raises ValueError if im was flipped across any axis. Flipping changes scanner origin, which is currently not handled
        """
        # Get orientation indicated by headers
        headers = im.headers
        if headers is None:
            raise ValueError(
                'MedicalVolume headers must be initialized to save as a dicom')

        affine = LPSplus_to_RASplus(headers)
        orientation = stdo.orientation_nib_to_standard(nib.aff2axcodes(affine))

        # Currently do not support mismatch in scanner_origin
        if tuple(affine[:3, 3]) != im.scanner_origin:
            raise ValueError(
                'Scanner origin mismatch. Currently we do not handle mismatch in scanner origin (i.e. cannot flip across axis)'
            )

        # reformat medical volume to expected orientation specified by dicom headers
        # store original orientation so we can undo the dicom-specific reformatting
        original_orientation = im.orientation

        im.reformat(orientation)
        volume = im.volume
        assert volume.shape[2] == len(
            headers), "Dimension mismatch - %d slices but %d headers" % (
                volume.shape[-1], len(headers))

        # check if filepath exists
        filepath = io_utils.check_dir(filepath)

        num_slices = len(headers)
        filename_format = 'I%0' + str(max(4, ceil(
            log10(num_slices)))) + 'd.dcm'

        for s in range(num_slices):
            s_filepath = os.path.join(filepath, filename_format % (s + 1))
            self.__write_dicom_file__(volume[..., s], headers[s], s_filepath)

        # reformat image to original orientation (before saving)
        # we do this, because saving should not affect the existing state of any variable
        im.reformat(original_orientation)
Example #9
0
    def __save_dir__(self, dirpath, create_dir=True):
        """Returns directory specific to this scan

        :param dirpath: base directory path to locate data directory for this scan
        :param create_dir: create the data directory
        :return: data directory for this scan type
        """
        name_len = len(self.NAME) + 2  # buffer
        if self.NAME in dirpath[-name_len:]:
            scan_dirpath = os.path.join(dirpath, '%s_data' % self.NAME)
        else:
            scan_dirpath = dirpath

        scan_dirpath = os.path.join(scan_dirpath, '%s_data' % self.NAME)

        if create_dir:
            scan_dirpath = io_utils.check_dir(scan_dirpath)

        return scan_dirpath
Example #10
0
    def __dilate_mask__(
            self,
            mask_path: str,
            temp_path: str,
            dil_rate: float = defaults.DEFAULT_MASK_DIL_RATE,
            dil_threshold: float = defaults.DEFAULT_MASK_DIL_THRESHOLD):
        """Dilate mask using gaussian blur and write to disk to use with elastix

        :param mask_path: path to mask to use to use as focus points for registration, mask must be binary
        :param temp_path: path to store temporary data
        :param dil_rate: dilation rate (sigma)
        :param dil_threshold: threshold to binarize dilated mask - float between [0, 1]
        :return: the path to the dilated mask

        :raise FileNotFoundError:
                    1. filepath specified by mask_path is not found
        :raise ValueError:
                    1. dil_threshold not in range [0, 1]
        """

        if not os.path.isfile(mask_path):
            raise FileNotFoundError('File %s not found' % mask_path)

        if dil_threshold < 0 or dil_threshold > 1:
            raise ValueError('dil_threshold must be in range [0, 1]')

        mask = fio_utils.generic_load(mask_path, expected_num_volumes=1)

        dilated_mask = sni.gaussian_filter(np.asarray(mask.volume,
                                                      dtype=np.float32),
                                           sigma=dil_rate) > dil_threshold
        fixed_mask = np.asarray(dilated_mask, dtype=np.int8)
        fixed_mask_filepath = os.path.join(io_utils.check_dir(temp_path),
                                           'dilated-mask.nii.gz')

        dilated_mask_volume = MedicalVolume(fixed_mask, affine=mask.affine)
        dilated_mask_volume.save_volume(fixed_mask_filepath)

        return fixed_mask_filepath
Example #11
0
    def __save_dir__(self, dirpath: str, create_dir: bool = True):
        """Returns directory specific to this scan

        :param dirpath: base directory path to locate data directory for this scan
        :param create_dir: create the data directory
        :return: data directory for this scan
        """
        # folder_id = '%s-%03d' % (self.NAME, self.series_number)
        folder_id = self.NAME

        name_len = len(folder_id) + 2  # buffer
        if self.NAME in dirpath[-name_len:]:
            scan_dirpath = os.path.join(dirpath, folder_id)
        else:
            scan_dirpath = dirpath

        scan_dirpath = os.path.join(scan_dirpath, folder_id)

        if create_dir:
            scan_dirpath = io_utils.check_dir(scan_dirpath)

        return scan_dirpath
Example #12
0
 def setUpClass(cls):
     cls.dicom_dirpath = get_dicoms_path(os.path.join(UNITTEST_SCANDATA_PATH, cls.SCAN_TYPE.NAME))
     cls.data_dirpath = get_data_path(os.path.join(UNITTEST_SCANDATA_PATH, cls.SCAN_TYPE.NAME))
     io_utils.check_dir(cls.data_dirpath)
Example #13
0
    def interregister(self, target_path, mask_path=None):
        temp_raw_dirpath = io_utils.check_dir(
            os.path.join(self.temp_path, 'raw'))
        subvolumes = self.subvolumes

        raw_filepaths = dict()

        echo_time_inds = natsorted(list(subvolumes.keys()))

        for i in range(len(echo_time_inds)):
            raw_filepath = os.path.join(temp_raw_dirpath, '%03d.nii.gz' % i)
            subvolumes[i].save_volume(raw_filepath)
            raw_filepaths[i] = raw_filepath

        # last echo should be base
        base_echo_time, base_image = len(echo_time_inds) - 1, raw_filepaths[
            len(echo_time_inds) - 1]

        temp_interregistered_dirpath = io_utils.check_dir(
            os.path.join(self.temp_path, 'interregistered'))

        print('')
        print('==' * 40)
        print('Interregistering...')
        print('Target: %s' % target_path)
        if mask_path is not None:
            print('Mask: %s' % mask_path)
        print('==' * 40)

        files_to_warp = []
        for echo_time_ind in raw_filepaths.keys():
            if echo_time_ind == base_echo_time:
                continue
            filepath = raw_filepaths[echo_time_ind]
            files_to_warp.append((echo_time_ind, filepath))

        if not mask_path:
            parameter_files = [
                fc.ELASTIX_RIGID_PARAMS_FILE, fc.ELASTIX_AFFINE_PARAMS_FILE
            ]
        else:
            parameter_files = [
                fc.ELASTIX_RIGID_INTERREGISTER_PARAMS_FILE,
                fc.ELASTIX_AFFINE_INTERREGISTER_PARAMS_FILE
            ]

        warped_file, transformation_files = self.__interregister_base_file__(
            (base_image, base_echo_time),
            target_path,
            temp_interregistered_dirpath,
            mask_path=mask_path,
            parameter_files=parameter_files)
        warped_files = [(base_echo_time, warped_file)]

        # Load the transformation file. Apply same transform to the remaining images
        for echo_time, filename in files_to_warp:
            warped_file = self.__apply_transform__(
                (filename, echo_time), transformation_files,
                temp_interregistered_dirpath)
            # append the last warped file - this has all the transforms applied
            warped_files.append((echo_time, warped_file))

        # copy each of the interregistered warped files to their own output
        subvolumes = dict()
        for echo_time, warped_file in warped_files:
            subvolumes[echo_time] = io_utils.load_nifti(warped_file)

        self.subvolumes = subvolumes
Example #14
0
 def setUpClass(cls):
     io_utils.check_dir(IO_UTILS_DATA)
Example #15
0
 def __save_dirpath__(self, dirpath):
     """Subdirectory to store data - save_dirpath/self.STR_ID/
     :param dirpath: base dirpath
     :return:
     """
     return io_utils.check_dir(os.path.join(dirpath, '%s' % self.STR_ID))
Example #16
0
    def __intraregister__(self, subvolumes):
        """Intraregister cubequant subvolumes to each other
        Patient could have moved between acquisition of different subvolumes, so different subvolumes of cubequant scan
            have to be registered with each other

        The first spin lock time has the highest SNR, so it is used as the target
        Subvolumes corresponding to the other spin lock times are registered to the target

        Affine registration is done using elastix

        :param subvolumes: dictionary of subvolumes mapping spin lock time index --> MedicalVolume
                                (e.g. {0 --> MedicalVolume A, 1 --> MedicalVolume B}
        :return: a dictionary of base, other files spin-lock index --> nifti filepath
        """

        if subvolumes is None:
            raise ValueError('subvolumes must be dict()')

        print('')
        print('==' * 40)
        print('Intraregistering...')
        print('==' * 40)

        # temporarily save subvolumes as nifti file
        ordered_spin_lock_time_indices = natsorted(list(subvolumes.keys()))
        raw_volumes_base_path = io_utils.check_dir(
            os.path.join(self.temp_path, 'raw'))

        # Use first spin lock time as a basis for registration
        spin_lock_nii_files = []
        for spin_lock_time_index in ordered_spin_lock_time_indices:
            filepath = os.path.join(raw_volumes_base_path,
                                    '%03d' % spin_lock_time_index + '.nii.gz')
            spin_lock_nii_files.append(filepath)

            subvolumes[spin_lock_time_index].save_volume(filepath)

        target_filepath = spin_lock_nii_files[0]

        intraregistered_files = []
        for i in range(1, len(spin_lock_nii_files)):
            spin_file = spin_lock_nii_files[i]
            spin_lock_time_index = ordered_spin_lock_time_indices[i]

            reg = Registration()
            reg.inputs.fixed_image = target_filepath
            reg.inputs.moving_image = spin_file
            reg.inputs.output_path = io_utils.check_dir(
                os.path.join(self.temp_path, 'intraregistered',
                             '%03d' % spin_lock_time_index))
            reg.inputs.parameters = [fc.ELASTIX_AFFINE_PARAMS_FILE]
            reg.terminal_output = fc.NIPYPE_LOGGING
            print('Registering %s --> %s' %
                  (str(spin_lock_time_index),
                   str(ordered_spin_lock_time_indices[0])))
            tmp = reg.run()

            warped_file = tmp.outputs.warped_file
            intraregistered_files.append((spin_lock_time_index, warped_file))

        return {
            'BASE':
            (ordered_spin_lock_time_indices[0], spin_lock_nii_files[0]),
            'FILES': intraregistered_files
        }