Ejemplo n.º 1
0
 def __init__(self,
              snapshots_directory: os.PathLike,
              snapshot_count: int = 0):
     self.generic_io = GenericIO()
     self.writer = ImageWriter(snapshots_directory)
     self.snapshot_count = snapshot_count
     self.logger = get_logger(__name__)
Ejemplo n.º 2
0
class ZipSurfaceIO(ABCSurfaceIO):
    """
    This writes contents of surface to txt files and zips them
    """
    logger = get_logger(__name__)

    def write(self, surface, filename):
        tmpdir = tempfile.TemporaryDirectory()

        file_vertices = os.path.join(tmpdir.name, 'vertices.txt')
        file_triangles = os.path.join(tmpdir.name, 'triangles.txt')
        file_normals = os.path.join(tmpdir.name, 'normals.txt')

        file_vox2ras = os.path.join(os.path.dirname(filename), 'vox2ras.txt')

        numpy.savetxt(file_vertices, surface.vertices, fmt='%.6f %.6f %.6f')
        numpy.savetxt(file_triangles, surface.triangles, fmt='%d %d %d')
        numpy.savetxt(file_normals, surface.vertex_normals(), fmt='%.6f %.6f %.6f')

        with ZipFile(filename, 'w') as zip_file:
            zip_file.write(file_vertices, os.path.basename(file_vertices))
            zip_file.write(file_triangles, os.path.basename(file_triangles))
            zip_file.write(file_normals, os.path.basename(file_normals))
            if os.path.exists(file_vox2ras):
                zip_file.write(file_vox2ras, os.path.basename(file_vox2ras))
            else:
                self.logger.warn("The %s file does not exist" % file_vox2ras)
Ejemplo n.º 3
0
class FreesurferIO(ABCSurfaceIO):
    """
    This class reads content of Freesurfer surface files
    """
    logger = get_logger(__name__)

    def read(self, surface_path, use_center_surface):
        vertices, triangles, metadata = read_geometry(
            surface_path, read_metadata=True)
        self.logger.info(
            "From the file %s the extracted metadata is %s", surface_path, metadata)

        if use_center_surface:
            cras = [0, 0, 0]
            self.logger.info(
                "The --center_ras flag was specified, so the ras centering point is %s", cras)
        else:
            if CENTER_RAS_FS_SURF in metadata:
                cras = metadata[CENTER_RAS_FS_SURF]
                self.logger.info(
                    "The ras centering point for surface %s is %s", surface_path, cras)
            else:
                cras = [0, 0, 0]
                self.logger.warning("Could not read the ras centering point from surface %s header. "
                                    "The cras will be %s", surface_path, cras)

        return Surface(vertices, triangles, area_mask=None,
                       center_ras=cras, generic_metadata=metadata)

    def write(self, surface, surface_path):
        write_geometry(filepath=surface_path, coords=surface.vertices, faces=surface.triangles,
                       volume_info=surface.get_main_metadata())

    def read_transformation_matrix_from_metadata(self, image_metadata):
        matrix_from_metadata = [[0, 0, 0, 0]
                                for _ in range(4)]  # or numpy.zeros((4,4))

        for i, fs_key in enumerate(TRANSFORM_MATRIX_FS_KEYS):
            for j in range(3):
                matrix_from_metadata[i][j] = image_metadata[fs_key][j]
        matrix_from_metadata[3][3] = 1
        matrix_from_metadata = numpy.transpose(matrix_from_metadata)
        return matrix_from_metadata

    def write_transformation_matrix(self, image_metadata):
        """
        We write the identity matrix to FS meta to avoid freeview rotations.
        :param image_metadata: meta to be corrected
        :return: image_metadata after change
        """
        identity_matrix = [[1.0, 0.0, 0.0],
                           [0.0, 1.0, 0.0],
                           [0.0, 0.0, 1.0],
                           [0.0, 0.0, 0.0]]
        for i, fs_key in enumerate(TRANSFORM_MATRIX_FS_KEYS):
            image_metadata[fs_key] = identity_matrix[i]
Ejemplo n.º 4
0
class H5SurfaceIO(ABCSurfaceIO):
    """
    This class reads content of H5 surface files
    """
    logger = get_logger(__name__)

    def read(self, h5_path, use_center_surface=False):
        h5_file = h5py.File(h5_path, 'r', libver='latest')
        vertices = h5_file['/vertices'][()]
        triangles = h5_file['/triangles'][()]
        h5_file.close()
        return Surface(vertices, triangles)
Ejemplo n.º 5
0
class H5VolumeIO(ABCVolumeIO):
    """
    This class reads content of a H5 file and returns a Volume Object
    """

    logger = get_logger(__name__)

    def read(self, volume_path):
        h5_file = h5py.File(volume_path, 'r', libver='latest')
        data = h5_file['/data'][()]
        h5_file.close()
        return Volume(data, [], None)
Ejemplo n.º 6
0
class VolumeIO(ABCVolumeIO):
    """
    This class reads content of a NIFTI file and returns a Volume Object
    """

    logger = get_logger(__name__)

    def read(self, volume_path):
        image = nibabel.load(volume_path)
        header = image.header
        data = image.get_data()
        affine_matrix = image.affine
        self.logger.info("The affine matrix extracted from volume %s is %s" %
                         (volume_path, affine_matrix))

        return Volume(data, affine_matrix, header)

    def write(self, out_volume_path, volume):
        image = nibabel.Nifti1Image(volume.data, volume.affine_matrix,
                                    volume.header)
        nibabel.save(image, out_volume_path)
Ejemplo n.º 7
0
class VolumeService(object):
    logger = get_logger(__name__)

    def __init__(self):
        self.annotation_service = AnnotationService()

    def vol_to_ext_surf_vol(self,
                            in_vol_path,
                            labels=None,
                            ctx=None,
                            out_vol_path=None,
                            labels_surf=None,
                            labels_inner='0'):
        """
        Separate the voxels of the outer surface of a structure, from the inner ones. Default behavior: surface voxels
        retain their label, inner voxels get the label 0, and the input file is overwritten by the output.
        """

        labels = self.annotation_service.read_input_labels(labels=labels,
                                                           ctx=ctx)
        number_of_labels = len(labels)
        # Set the labels for the surfaces
        if labels_surf is None:
            labels_surf = labels
        else:
            # Read the surface labels and make sure there is one for each label
            labels_surf = numpy.array(labels_surf.split()).astype('i')
            if len(labels_surf) == 1:
                labels_surf = numpy.repeat(labels_inner,
                                           number_of_labels).tolist()
            elif len(labels_surf) != number_of_labels:
                self.logger.warning(
                    "Output labels for surface voxels are neither of length "
                    "1 nor of length equal to the one of target labels.")
                return
            else:
                labels_surf = labels_surf.tolist()
        # Read the inner, non-surface labels
        labels_inner = numpy.array(labels_inner.split()).astype('i')
        # ...and make sure there is one for each label
        if len(labels_inner) == 1:
            labels_inner = numpy.repeat(labels_inner,
                                        number_of_labels).tolist()
        elif len(labels_inner) != number_of_labels:
            self.logger.warning(
                "Output labels for inner voxels are neither of length 1 nor "
                "of length equal to the one of the target labels.")
            return
        else:
            labels_inner = labels_inner.tolist()

        # Read the input volume...
        volume = IOUtils.read_volume(in_vol_path)

        # Neigbors' grid sharing a face
        eye3 = numpy.identity(3)
        border_grid = numpy.c_[eye3, -eye3].T.astype('i')
        n_border = 6

        out_volume = Volume(numpy.array(volume.data), volume.affine_matrix,
                            volume.header)

        # Initialize output indexes
        out_ijk = []

        for label_index in range(number_of_labels):
            current_label = labels[label_index]
            # Get the indexes of all voxels of this label:
            label_volxels_i, label_voxels_j, label_voxels_k = numpy.where(
                volume.data == current_label)
            # and for each voxel
            for voxel_index in range(label_volxels_i.size):
                # indexes of this voxel:
                current_voxel_i, current_voxel_j, current_voxel_k = \
                    label_volxels_i[voxel_index], label_voxels_j[
                        voxel_index], label_voxels_k[voxel_index]
                # Create the neighbors' grid sharing a face
                ijk_grid = border_grid + \
                    numpy.tile(numpy.array(
                        [current_voxel_i, current_voxel_j, current_voxel_k]), (n_border, 1))
                # Remove voxels outside the image
                indices_inside_image = numpy.all(
                    [(ijk_grid[:, 0] >= 0),
                     (ijk_grid[:, 0] < volume.dimensions[0]),
                     (ijk_grid[:, 1] >= 0),
                     (ijk_grid[:, 1] < volume.dimensions[1]),
                     (ijk_grid[:, 2] >= 0),
                     (ijk_grid[:, 2] < volume.dimensions[2])],
                    axis=0)
                ijk_grid = ijk_grid[indices_inside_image, :]
                try:
                    # If all face neighbors are of the same label...
                    if numpy.all(
                            volume.data[ijk_grid[:, 0], ijk_grid[:, 1],
                                        ijk_grid[:, 2]] == numpy.tile(
                                            volume.data[current_voxel_i,
                                                        current_voxel_j,
                                                        current_voxel_k], (
                                                            n_border, 1))):
                        # ...set this voxel to the corresponding inner target label
                        out_volume.data[
                            current_voxel_i, current_voxel_j,
                            current_voxel_k] = labels_inner[label_index]
                    else:
                        # ...set this voxel to the corresponding surface target label
                        out_volume.data[
                            current_voxel_i, current_voxel_j,
                            current_voxel_k] = labels_surf[label_index]
                        out_ijk.append([
                            current_voxel_i, current_voxel_j, current_voxel_k
                        ])
                except ValueError:  # empty grid
                    self.logger.error(
                        "Error at voxel ( %s, %s, %s ) of label %s: It appears to have no common-face "
                        "neighbors inside the image!", str(current_voxel_i),
                        str(current_voxel_j), str(current_voxel_k),
                        str(current_label))
                    return

        if out_vol_path is None:
            out_vol_path = in_vol_path

        IOUtils.write_volume(out_vol_path, out_volume)

        # save the output indexes that survived masking
        out_ijk = numpy.vstack(out_ijk)
        filepath = os.path.splitext(out_vol_path)[0]
        numpy.save(filepath + "-idx.npy", out_ijk)
        numpy.savetxt(filepath + "-idx.txt", out_ijk, fmt='%d')

    def mask_to_vol(self,
                    in_vol_path,
                    mask_vol_path,
                    out_vol_path=None,
                    labels=None,
                    ctx=None,
                    vol2mask_path=None,
                    vn=1,
                    th=0.999,
                    labels_mask=None,
                    labels_nomask='0'):
        """
        Identify the voxels that are neighbors within a voxel distance vn, to a mask volume, with a mask threshold of th
        Default behavior: we assume a binarized mask and set th=0.999, no neighbors search, only looking at the exact
        voxel position, i.e., vn=0. Accepted voxels retain their label, whereas rejected ones get a label of 0
        """

        # Set the target labels:
        labels = self.annotation_service.read_input_labels(labels=labels,
                                                           ctx=ctx)
        number_of_labels = len(labels)
        # Set the labels for the selected voxels
        if labels_mask is None:
            labels_mask = labels

        else:
            # Read the labels and make sure there is one for each label
            labels_mask = numpy.array(labels_mask.split()).astype('i')

            if len(labels_mask) == 1:
                labels_mask = numpy.repeat(labels_mask,
                                           number_of_labels).tolist()

            elif len(labels_mask) != number_of_labels:
                self.logger.warning(
                    "Output labels for selected voxels are neither of length 1 nor of length equal to "
                    "the one of target labels")
                return

            else:
                labels_mask = labels_mask.tolist()

        # Read the excluded labels and make sure there is one for each label
        labels_nomask = numpy.array(labels_nomask.split()).astype('i')
        if len(labels_nomask) == 1:
            labels_nomask = numpy.repeat(labels_nomask,
                                         number_of_labels).tolist()

        elif len(labels_nomask) != number_of_labels:
            self.logger.warning(
                "Output labels for excluded voxels are neither of length 1 nor of length equal to the "
                "one of the target labels")
            return

        else:
            labels_nomask = labels_nomask.tolist()

        volume = IOUtils.read_volume(in_vol_path)

        mask_vol = IOUtils.read_volume(mask_vol_path)

        # Compute the transform from vol ijk to mask ijk:
        ijk2ijk = numpy.identity(4)

        # If vol and mask are not in the same space:
        if os.path.exists(str(vol2mask_path)):
            # read the xyz2xyz transform and apply it to the inverse mask
            # affine transform to get an ijk2ijk transform.
            xyz2xyz = numpy.loadtxt(vol2mask_path)
            ijk2ijk = volume.affine_matrix.dot(
                numpy.dot(xyz2xyz, numpy.linalg.inv(mask_vol.affine_matrix)))

        # Construct a grid template of voxels +/- vn voxels around each ijk
        # voxel, sharing at least a corner
        grid = numpy.meshgrid(list(range(-vn, vn + 1, 1)),
                              list(range(-vn, vn + 1, 1)),
                              list(range(-vn, vn + 1, 1)),
                              indexing='ij')
        grid = numpy.c_[numpy.array(grid[0]).flatten(),
                        numpy.array(grid[1]).flatten(),
                        numpy.array(grid[2]).flatten()]
        n_grid = grid.shape[0]

        out_volume = Volume(numpy.array(volume.data), volume.affine_matrix,
                            volume.header)

        # Initialize output indexes
        out_ijk = []

        # For each target label:
        for label_index in range(number_of_labels):
            current_label = labels[label_index]
            # Get the indexes of all voxels of this label:
            label_voxels_i, label_voxels_j, label_voxels_k = numpy.where(
                volume.data == current_label)

            for voxel_index in range(label_voxels_i.size):
                current_voxel_i, current_voxel_j, current_voxel_k = \
                    label_voxels_i[voxel_index], label_voxels_j[
                        voxel_index], label_voxels_k[voxel_index]
                # TODO if necessary: deal with voxels at the edge of the image, such as brain stem ones...
                #     if any([(i==0), (i==mask_shape[0]-1),(j==0), (j==mask_shape[0]-1),(k==0), (k==mask_shape[0]-1)]):
                #               mask_shape[i,j,k]=0
                #               continue

                # ...get the corresponding voxel in the mask volume:
                ijk = numpy.round(
                    ijk2ijk.dot(
                        numpy.array([
                            current_voxel_i, current_voxel_j, current_voxel_k,
                            1
                        ]))[:3]).astype('i')

                # Make sure this point is within image limits
                for cc in range(3):
                    if ijk[cc] < 0:
                        ijk[cc] = 0

                    elif ijk[cc] >= mask_vol.dimensions[cc]:
                        ijk[cc] = mask_vol.dimensions[cc] - 1

                # If this is a voxel to keep, set it so...
                if mask_vol.data[ijk[0], ijk[1], ijk[2]] >= th:
                    out_volume.data[current_voxel_i, current_voxel_j,
                                    current_voxel_k] = labels_mask[label_index]
                    out_ijk.append(
                        [current_voxel_i, current_voxel_j, current_voxel_k])

                elif vn > 0:
                    # If not, and as long as vn>0 check whether any of its vn neighbors is a mask voxel.
                    # Generate the specific grid centered at the vertex ijk
                    ijk_grid = grid + numpy.tile(ijk, (n_grid, 1))

                    # Remove voxels outside the mask volume
                    indexes_within_limits = numpy.all(
                        [(ijk_grid[:, 0] >= 0),
                         (ijk_grid[:, 0] < mask_vol.dimensions[0]),
                         (ijk_grid[:, 1] >= 0),
                         (ijk_grid[:, 1] < mask_vol.dimensions[1]),
                         (ijk_grid[:, 2] >= 0),
                         (ijk_grid[:, 2] < mask_vol.dimensions[2])],
                        axis=0)
                    ijk_grid = ijk_grid[indexes_within_limits, :]

                    try:
                        # If none of these points is a mask point:
                        if (mask_vol.data[ijk_grid[:, 0], ijk_grid[:, 1],
                                          ijk_grid[:, 2]] < th).all():
                            out_volume.data[
                                current_voxel_i, current_voxel_j,
                                current_voxel_k] = labels_nomask[label_index]

                        else:  # if any of them is a mask point:
                            out_volume.data[
                                current_voxel_i, current_voxel_j,
                                current_voxel_k] = labels_mask[label_index]
                            out_ijk.append([
                                current_voxel_i, current_voxel_j,
                                current_voxel_k
                            ])

                    except ValueError:  # empty grid
                        self.logger.error(
                            "Error at voxel ( %s, %s, %s ): It appears to have no common-face neighbors "
                            "inside the image!", str(current_voxel_i),
                            str(current_voxel_j), str(current_voxel_k))
                        return

                else:
                    out_volume.data[
                        current_voxel_i, current_voxel_j,
                        current_voxel_k] = labels_nomask[label_index]

        if out_vol_path is None:
            out_vol_path = in_vol_path

        IOUtils.write_volume(out_vol_path, out_volume)

        # Save the output indexes that survived masking
        out_ijk = numpy.vstack(out_ijk)
        filepath = os.path.splitext(out_vol_path)[0]
        numpy.save(filepath + "-idx.npy", out_ijk)
        numpy.savetxt(filepath + "-idx.txt", out_ijk, fmt='%d')

    def label_with_dilation(self, to_label_nii_fname, dilated_nii_fname,
                            out_nii_fname):
        """
        Labels a volume using its labeled dilation. The dilated volume is labeled using scipy.ndimage.label function.
        :param to_label_nii_fname: usually a CT-mask.nii.gz
        :param dilated_nii_fname: dilated version of the to_label_nii_fname volume
        """

        # TODO could make dilation with ndimage also.
        mask = IOUtils.read_volume(to_label_nii_fname)
        dil_mask = IOUtils.read_volume(dilated_nii_fname)

        lab, n = scipy.ndimage.label(dil_mask.data)
        mask.data *= lab
        self.logger.info('%d objects found when labeling the dilated volume.',
                         n)

        IOUtils.write_volume(out_nii_fname, mask)

    def _label_config(self, aparc):
        unique_data = numpy.unique(aparc.data)
        unique_data_map = numpy.r_[:unique_data.max() + 1]
        unique_data_map[unique_data] = numpy.r_[:unique_data.size]
        aparc.data = unique_data_map[aparc.data]
        return aparc

    def simple_label_config(self, in_aparc_path, out_volume_path):
        """
        Relabel volume to have contiguous values like Mrtrix' labelconfig.
        :param in_aparc_path: volume voxel value is the index of the region it belongs to.
        :return: writes the labeled volume to out_volume_path.
        """

        aparc = IOUtils.read_volume(in_aparc_path)
        aparc = self._label_config(aparc)
        IOUtils.write_volume(out_volume_path, aparc)

    def _label_volume(self, tdi_volume, lo=0.5):
        mask = tdi_volume.data > lo
        tdi_volume.data[~mask] = 0
        tdi_volume.data[mask] = numpy.r_[1:mask.sum() + 1]
        return tdi_volume

    def label_vol_from_tdi(self, tdi_volume_path, out_volume_path, lo=0.5):
        """
        Creates a mask of the voxels with tract ends > lo and any other voxels become 0.
        Labels each voxel different from 0 with integer labels starting from 1.
        :param tdi_volume_path: volume voxel value is the sum of tract ends. Voxel without tract ends has value 0.
        :param lo: tract ends threshold used for masking.
        :return: writes labeled volume to :ut_volume_path.
        """

        nii_volume = IOUtils.read_volume(tdi_volume_path)
        tdi_volume = self._label_volume(nii_volume, lo)
        IOUtils.write_volume(out_volume_path, tdi_volume)

    def remove_zero_connectivity_nodes(self,
                                       node_volume_path,
                                       connectivity_matrix_path,
                                       tract_length_path=None):
        """
        It removes network nodes with zero connectivity from the volume and connectivity matrices.
        The zero connectivity nodes will be labeled with 0 in the volume and the remaining labels will be updated.
        The connectivity matrices will be symmetric.
        :param node_volume_path: tdi_lbl.nii volume path
        :param connectivity_matrix_path: .csv file, output of Mrtrix3 tck2connectome
        :param tract_length_path: optional .csv tract lengths matrix
        :return: overwrites the input volume and matrices with the processed ones. Also saves matrices as .npy.
        """

        node_volume = IOUtils.read_volume(node_volume_path)

        connectivity = numpy.array(
            numpy.genfromtxt(connectivity_matrix_path, dtype='int64'))
        connectivity = connectivity + connectivity.T
        connectivity_row_sum = numpy.sum(connectivity, axis=0)

        nodes_to_keep_indices = connectivity_row_sum > 0
        connectivity = connectivity[
            nodes_to_keep_indices, :][:, nodes_to_keep_indices]

        numpy.save(
            os.path.splitext(connectivity_matrix_path)[0] + NPY_EXTENSION,
            connectivity)
        numpy.savetxt(connectivity_matrix_path, connectivity, fmt='%1d')

        if os.path.exists(str(tract_length_path)):
            connectivity = numpy.array(
                numpy.genfromtxt(tract_length_path, dtype='int64'))
            connectivity = connectivity[
                nodes_to_keep_indices, :][:, nodes_to_keep_indices]

            numpy.save(
                os.path.splitext(tract_length_path)[0] + NPY_EXTENSION,
                connectivity)
            numpy.savetxt(tract_length_path, connectivity, fmt='%1d')

        else:
            self.logger.warning("Path %s is not valid.", tract_length_path)

        nodes_to_remove_indices, = numpy.where(~nodes_to_keep_indices)
        nodes_to_remove_indices += 1

        for node_index in nodes_to_remove_indices:
            node_volume.data[node_volume.data == node_index] = 0

        node_volume.data[node_volume.data > 0] = numpy.r_[1:(
            connectivity.shape[0] + 1)]

        IOUtils.write_volume(node_volume_path, node_volume)

    def con_vox_in_ras(self, ref_vol_path):
        """
        This function reads a tdi_lbl volume and returns the voxels that correspond to connectome nodes,
        and their coordinates in ras space, simply by applying the affine transform of the volume
        :param ref_vol_path: the path to the tdi_lbl volume
        :return: vox and voxxyz,
                i.e., the labels (integers>=1) and the coordinates of the connnectome nodes-voxels, respectively
        """
        # Read the reference tdi_lbl volume:
        vollbl = IOUtils.read_volume(ref_vol_path)
        vox = vollbl.data.astype('i')
        # Get only the voxels that correspond to connectome nodes:
        voxijk, = numpy.where(vox.flatten() > 0)
        voxijk = numpy.unravel_index(voxijk, vollbl.dimensions)
        vox = vox[voxijk[0], voxijk[1], voxijk[2]]
        # ...and their coordinates in ras xyz space
        voxxzy = vollbl.affine_matrix.dot(
            numpy.c_[voxijk[0], voxijk[1], voxijk[2],
                     numpy.ones(vox.shape[0])].T)[:3].T
        return vox, voxxzy
Ejemplo n.º 8
0
class SurfaceService(object):
    logger = get_logger(__name__)

    def __init__(self):
        self.annotation_service = AnnotationService()

    # #TODO: transfer the following two to some general services?:
    # def mask2index(self,mask):
    #     """
    #     Convert a mask to an array of indices
    #     :param mask:
    #     :return:
    #     """
    #     return numpy.where(mask)
    #
    # def index2mask(self,index):
    #     """
    #     Convert an array of indices to the corresponding mask
    #     :param index:
    #     :return:
    #     """
    #     index=numpy.unique(index)
    #     mask=numpy.zeros((len(index,))).astype('bool')
    #     mask[index]=True
    #     return mask

    def tri_area(self, tri):
        i, j, k = numpy.transpose(tri, (1, 0, 2))
        ij = j - i
        ik = k - i
        return numpy.sqrt(numpy.sum(numpy.cross(ij, ik)**2, axis=1)) / 2.0

    def convert_fs_to_brain_visa(self, in_surf_path):
        surface = IOUtils.read_surface(in_surf_path, False)
        IOUtils.write_surface(in_surf_path + '.tri', surface)

    def convert_bem_to_tri(self, surfaces_directory_path):
        surfs_glob = '%s/*_surface-low' % (surfaces_directory_path)
        for surf_name in glob.glob(surfs_glob):
            self.convert_fs_to_brain_visa(surf_name)

    def merge_surfaces(self, surfaces):
        """
        Merge several surfaces, and their region mappings.
        :return: the merge result surface and region mapping.
        """
        n_surfaces = len(surfaces)
        out_surface = Surface([], [])
        # TODO: how to deal with the metadata of merged surfaces, so that freesurfer.io can handle them, e.g., write them
        # i.e., we need to have a final unique version of the metadata, not a list of them, as I am doing here in the
        # commented code
        # out_surface_attributes=dict()
        # for attribute in ["vertices_coord_system", "generic_metadata", "vertices_metadata", "triangles_metadata"]:
        #     out_surface_attributes[attribute]=[]
        for i_srf in range(n_surfaces):
            out_surface.add_vertices_and_triangles(surfaces[i_srf].vertices,
                                                   surfaces[i_srf].triangles,
                                                   surfaces[i_srf].area_mask)
            if len(surfaces[i_srf].center_ras) == 0:
                pass
            elif len(out_surface.center_ras) == 0:
                out_surface.center_ras = surfaces[i_srf].center_ras
            elif numpy.any(
                    out_surface.center_ras != surfaces[i_srf].center_ras):
                raise ValueError(
                    "At least two surfaces have different -non empty- centers in RAS coordinates!"
                )
            # #TODO: think about how to better merge these fields
            # for attribute in ["vertices_coord_system", "generic_metadata", "vertices_metadata", "triangles_metadata"]:
            #     out_surface_attributes[attribute].append(getattr(surfaces[i_srf],attribute))
            #     setattr(out_surface,attribute,out_surface_attributes[attribute])
        return out_surface

    def compute_gdist_mat(self, surf_name='pial', max_distance=40.0):
        max_distance = float(max_distance)  # in case passed from sys.argv
        for h in 'rl':
            subjects_dir = os.environ['SUBJECTS_DIR']
            subject = os.environ['SUBJECT']
            surf_path = '%s/%s/surf/%sh.%s' % (subjects_dir, subject, h,
                                               surf_name)
            surface = IOUtils.read_surface(surf_path, False)
            mat_path = '%s/%s/surf/%sh.%s.gdist.mat' % (subjects_dir, subject,
                                                        h, surf_name)
            mat = gdist.local_gdist_matrix(surface.vertices,
                                           surface.triangles.astype('<i4'),
                                           max_distance=max_distance)
            scipy.io.savemat(mat_path, {'gdist': mat})

    # TODO: maybe create a new "connectome" service and transfer this function there
    # TODO: add more normalizations modes
    def compute_geodesic_dist_affinity(self, dist, norm=None):
        """
        This function calculates geodesic distances among nodes of a mesh,
        starting from the array of the distances between directly connected nodes.
        Optionally, normalization with the maximum geodesic distances is performed.
        Infinite distances (corresponding to disconnected components of the mesh, are not allowed)
        :param dist: a dense array of distances between directly connected nodes of a mesh/network
        :param norm: a flag to be currently used for optional normalization with the maximum geodesic distance

        :return:
        """
        # TODO: make sure that this returns a symmetric matrix!
        geodist = shortest_path(dist,
                                method='auto',
                                directed=False,
                                return_predecessors=False,
                                unweighted=False,
                                overwrite=False).astype('single')
        # Find the maximum non infinite geodesic distance:
        max_gdist = numpy.max(geodist, axis=None)
        assert numpy.isfinite(max_gdist)
        if norm is not None:
            geodist /= max_gdist
        # Convert them to normalized distances and return them
        return geodist

    def extract_subsurf(self, surface, verts_mask, output='surface'):
        """
        Extracts a sub-surface that contains only the masked vertices and the corresponding faces.
        An important step is to replace old vertices indexes of faces to the new ones.
        :param: surface: input surface object
        :param: verts_mask: mask of the sub-surface to be extracted
        :return: output surface object
        """

        verts_out = surface.vertices[verts_mask]
        triangles_mask = numpy.c_[verts_mask[surface.triangles[:, 0]],
                                  verts_mask[surface.triangles[:, 1]],
                                  verts_mask[surface.triangles[:,
                                                               2]]].all(axis=1)
        triangles_out = numpy.array(surface.triangles[triangles_mask, :])
        verts_out_inds, = numpy.where(verts_mask)
        for triang_idx in range(triangles_out.shape[0]):
            for vertex_idx in range(3):
                triangles_out[triang_idx, vertex_idx], = \
                    numpy.where(
                        triangles_out[triang_idx, vertex_idx] == verts_out_inds)
        if output == 'surface':
            out_surface = Surface(
                verts_out,
                triangles_out,
                area_mask=surface.area_mask[verts_out_inds],
                center_ras=surface.center_ras,
                vertices_coord_system=surface.vertices_coord_system,
                generic_metadata=surface.generic_metadata,
                vertices_metadata=surface.vertices_metadata,
                triangles_metadata=surface.triangles_metadata)
            return out_surface
        else:
            return (verts_out, triangles_out,
                    surface.area_mask[verts_out_inds])

    def compute_surface_area(self, surface, area_mask=None):
        """
            This function computes the surface area, after optionally applying a mask to choose a sub-surface
            :param: surface: input surface object
            :param area_mask: optional boolean mask (number of vertices x ) to overwrite the surface.area_mask
            :return: (sub)surface area, float
            """
        if area_mask is None:
            area_mask = surface.area_mask
        # Apply the mask in order to extract the sub-surface (vertices and
        # relevant triangles)
        (vertices,
         triangles) = self.extract_subsurf(surface,
                                           area_mask,
                                           output='verts_triangls')[:2]
        return numpy.sum(self.tri_area(vertices[triangles]))

    def vertex_connectivity(self,
                            surface,
                            mode="sparse",
                            metric=None,
                            symmetric=False,
                            verts_mask=None):
        """
        It computes a sparse matrix of the connectivity among the vertices of a surface.
        :param surface: input surface object
        :param mode: "sparse" by default or "2D"
        :param metric: None by default, could be "euclidean"
        :param symmetric: True for symmetric matrix output
        :param verts_mask: a mask to apply the method to a a sub-surface of the original surface
        :return: the computed matrix.
        """
        if verts_mask is not None:
            (vertices,
             triangles) = self.extract_subsurf(surface,
                                               verts_mask,
                                               output='verts_triangls')[:2]
        else:
            vertices = surface.vertices
            triangles = surface.triangles
        # Get all pairs of vertex indexes (i.e., edges) that appear in each
        # face (triangle)
        edges = numpy.r_[triangles[:, [0, 1]], triangles[:, [1, 2]],
                         triangles[:, [2, 0]]]
        # Remove repetitions
        edges = numpy.vstack(set(map(tuple, edges)))
        # Mark all existing pairs to 1
        n_v = vertices.shape[0]
        n_e = edges.shape[0]
        # For symmetric output...
        if symmetric:
            #...create for the moment the "double" edges
            edges2 = numpy.r_[edges, edges[:, [1, 0]]]
        if metric is None:
            # For symmetric output...
            if symmetric:
                # ...remove repetitions of edges2
                edges = numpy.vstack(set(map(tuple, edges2)))
                n_e = edges.shape[0]
            con = csr_matrix((numpy.ones((n_e, )), (edges[:, 0], edges[:, 1])),
                             shape=(n_v, n_v))
            if mode != "sparse":
                # Create non-sparse matrix
                con = con.todense()
        else:
            d = paired_distances(vertices[edges[:, 0]], vertices[edges[:, 1]],
                                 metric)
            # For symmetric output...
            if symmetric:
                # double also d...
                d = numpy.r_[d, d]
                edges = edges2
            if mode == "sparse":
                # Create sparse matrix
                con = csr_matrix((d, (edges[:, 0], edges[:, 1])),
                                 shape=(n_v, n_v))
        return con

    # TODO: use surface instead of verts and faces?? Denis: not sure about
    # this!..
    def connected_surface_components(self,
                                     surface=None,
                                     connectivity=None,
                                     verts_mask=None):
        """
        This function returns all the different disconnected components of a surface, their number and their areas,
        after applying an optional boolean mask to exclude some subsurface from the whole computation.
        There should be at least one component returned, if the whole surface is connected.
        :param surface: input surface object
        :param connectivity: optionally an array or sparse matrix of structural connectivity constraints,
                            where True or 1 or entry>0 stands for the existing direct connections
                            among neighboring vertices (i.e., vertices of a common triangular face)
        :param verts_mask: optional boolean mask (number of vertices x ) for vertices to include to the input surface
        :return:
        """
        if (surface is None) and connectivity is None:
            print(
                "Error: neither a surface, nor a connectivity matrix in the input!"
            )
            return 0
        elif connectivity is None:
            n_verts = surface.vertices.shape[0]
            # Create the connectivity matrix, if not in the input:
            connectivity = self.vertex_connectivity(surface,
                                                    verts_mask=verts_mask)
            if verts_mask is None:
                verts_mask = numpy.ones((n_verts, ), dtype=bool)
        else:
            n_verts = connectivity.shape[0]
            if verts_mask is None:
                verts_mask = numpy.ones((n_verts, ), dtype=bool)
            else:
                connectivity = connectivity[verts_mask, :][:, verts_mask]
        # Find all connected components of this surface
        (n_components, components_masked) = \
            connected_components(connectivity, directed=False,
                                 connection='weak', return_labels=True)
        comp_area = []
        if surface is not None:
            # For each component...
            for ic in range(n_components):
                i_comp_verts = components_masked == ic
                # ...compute the surface area, after applying any specified mask
                comp_area.append(
                    self.compute_surface_area(surface,
                                              mask=numpy.logical_and(
                                                  i_comp_verts,
                                                  surface.area_mask)))
        # Prepare final components' labels output:
        components = -numpy.ones((n_verts, )).astype('i')
        components[verts_mask] = components_masked
        return n_components, components, comp_area

    def aseg_surf_conc_annot(self,
                             surf_path,
                             out_surf_path,
                             annot_path,
                             label_indices,
                             lut_path=None):
        """
        Concatenate surfaces of one specific label of interest each, to create a single annotated surface.
        """

        lut_path = lut_path or default_lut_path()

        label_names, color_table = self.annotation_service.lut_to_annot_names_ctab(
            lut_path=lut_path, labels=label_indices)
        label_indices = numpy.array(label_indices.split()).astype('i')

        #                  verts tri area_mask cras
        surfaces = []
        out_annotation = Annotation([], [], [])
        label_number = -1

        for label_index in label_indices:
            this_surf_path = surf_path + "-%06d" % int(label_index)

            if os.path.exists(this_surf_path):
                ind_l, = numpy.where(label_indices == label_index)
                out_annotation.add_region_names_and_colors(
                    numpy.array(label_names)[ind_l], color_table[ind_l, :])
                label_number += 1
                surfaces.append(IOUtils.read_surface(this_surf_path, False))
                out_annotation.add_region_mapping(label_number * numpy.ones(
                    (surfaces[-1].n_vertices, ), dtype='int64'))
        out_surface = self.merge_surfaces(surfaces)
        #out_annotation.regions_color_table = numpy.squeeze(numpy.array(out_annotation.regions_color_table).astype('i'))

        IOUtils.write_surface(out_surf_path, out_surface)
        IOUtils.write_annotation(annot_path, out_annotation)

    def __prepare_grid(self, vertex_neighbourhood):
        # Prepare grid if needed for possible use:
        if vertex_neighbourhood > 0:
            grid = numpy.meshgrid(
                list(range(-vertex_neighbourhood, vertex_neighbourhood + 1,
                           1)),
                list(range(-vertex_neighbourhood, vertex_neighbourhood + 1,
                           1)),
                list(range(-vertex_neighbourhood, vertex_neighbourhood + 1,
                           1)),
                indexing='ij')
            grid = numpy.c_[numpy.array(grid[0]).flatten(),
                            numpy.array(grid[1]).flatten(),
                            numpy.array(grid[2]).flatten()]
            n_grid = grid.shape[0]

            return grid, n_grid

    def sample_vol_on_surf(self,
                           surf_path,
                           vol_path,
                           annot_path,
                           out_surf_path,
                           cras_path,
                           add_string='',
                           vertex_neighbourhood=1,
                           add_lbl=[],
                           lut_path=None):
        """
        Sample a volume of a specific label on a surface, by keeping only those surface vertices, the nearest voxel of
        which is of the given label (+ of possibly additional target labels, such as white matter).
        Allow optionally for vertices within a given voxel distance vn from the target voxels.
        """

        lut_path = lut_path or default_lut_path()

        # Read the inputs
        surface = IOUtils.read_surface(surf_path, False)

        annotation = IOUtils.read_annotation(annot_path)
        labels = self.annotation_service.annot_names_to_labels(
            annotation.region_names, add_string=add_string, lut_path=lut_path)
        region_mapping_indexes = numpy.unique(annotation.region_mapping)

        volume_parser = VolumeIO()
        volume = volume_parser.read(vol_path)
        ras2vox_affine_matrix = numpy.linalg.inv(volume.affine_matrix)

        cras = numpy.loadtxt(cras_path)

        grid, n_grid = self.__prepare_grid(vertex_neighbourhood)

        # Initialize the output mask:
        verts_out_mask = numpy.repeat([False], surface.vertices.shape[0])
        for label_index in range(len(region_mapping_indexes)):

            self.logger.info("%s",
                             add_string + annotation.region_names[label_index])

            # Get the indexes of the vertices corresponding to this label:
            verts_indices_of_label, = numpy.where(
                annotation.region_mapping[:] ==
                region_mapping_indexes[label_index])
            verts_indices_of_label_size = verts_indices_of_label.size
            if verts_indices_of_label_size == 0:
                continue

            # Add any additional labels
            all_labels = [labels[label_index]] + add_lbl

            # get the vertices for current label and add cras to take them to
            # scanner ras
            verts_of_label = surface.vertices[verts_indices_of_label, :]
            verts_of_label += numpy.repeat(numpy.expand_dims(cras, 1).T,
                                           verts_indices_of_label_size,
                                           axis=0)

            # Compute the nearest voxel coordinates using the affine transform
            ijk = numpy.round(
                ras2vox_affine_matrix.dot(numpy.c_[verts_of_label, numpy.ones(verts_indices_of_label_size)].T)[:3].T) \
                .astype('i')

            # Get the labels of these voxels:
            surf_vxls = volume.data[ijk[:, 0], ijk[:, 1], ijk[:, 2]]

            # Vertex mask to keep: those that correspond to voxels of one of
            # the target labels
            # surf_vxls==lbl if only one target label
            verts_keep, = numpy.where(numpy.in1d(surf_vxls, all_labels))
            verts_out_mask[verts_indices_of_label[verts_keep]] = True

            if vertex_neighbourhood > 0:
                # These are now the remaining indexes to be checked for
                # neighboring voxels
                verts_indices_of_label = numpy.delete(verts_indices_of_label,
                                                      verts_keep)
                ijk = numpy.delete(ijk, verts_keep, axis=0)

                for vertex_index in range(verts_indices_of_label.size):
                    # Generate the specific grid centered at the voxel ijk
                    ijk_grid = grid + \
                        numpy.tile(ijk[vertex_index, :], (n_grid, 1))

                    # Remove voxels outside the volume
                    indexes_within_limits = numpy.all(
                        [(ijk_grid[:, 0] >= 0),
                         (ijk_grid[:, 0] < volume.dimensions[0]),
                         (ijk_grid[:, 1] >= 0),
                         (ijk_grid[:, 1] < volume.dimensions[1]),
                         (ijk_grid[:, 2] >= 0),
                         (ijk_grid[:, 2] < volume.dimensions[2])],
                        axis=0)
                    ijk_grid = ijk_grid[indexes_within_limits, :]
                    surf_vxls = volume.data[ijk_grid[:, 0], ijk_grid[:, 1],
                                            ijk_grid[:, 2]]

                    # If any of the neighbors is of the target labels include
                    # the current vertex
                    # surf_vxls==lbl if only one target label
                    if numpy.any(numpy.in1d(surf_vxls, all_labels)):
                        verts_out_mask[
                            verts_indices_of_label[vertex_index]] = True

        # Vertex indexes and vertices to keep:
        verts_out_indices, = numpy.where(verts_out_mask)
        verts_out = surface.vertices[verts_out_indices]

        # TODO maybe: make sure that all voxels of this label correspond to at least one vertex.
        # Create a similar mask for faces by picking only triangles of which
        # all 3 vertices are included
        face_out_mask = numpy.c_[verts_out_mask[surface.triangles[:, 0]],
                                 verts_out_mask[surface.triangles[:, 1]],
                                 verts_out_mask[surface.triangles[:, 2]]].all(
                                     axis=1)
        faces_out = surface.triangles[face_out_mask]

        # The old vertices' indexes of faces have to be transformed to the new
        # vrtx_out_inds:
        for iF in range(faces_out.shape[0]):
            for vertex_index in range(3):
                faces_out[iF, vertex_index], = numpy.where(
                    faces_out[iF, vertex_index] == verts_out_indices)

        surface.vertices = verts_out
        surface.triangles = faces_out

        # Write the output surfaces and annotations to files. Also write files
        # with the indexes of vertices to keep.
        IOUtils.write_surface(out_surf_path, surface)

        annotation.set_region_mapping(
            annotation.get_region_mapping_by_indices([verts_out_indices]))
        IOUtils.write_annotation(out_surf_path + ".annot", annotation)

        numpy.save(out_surf_path + "-idx.npy", verts_out_indices)
        numpy.savetxt(out_surf_path + "-idx.txt", verts_out_indices, fmt='%d')

    # TODO: maybe create a new "connectome" service and transfer this function
    # there
    def compute_consim_affinity(self, verts, vox, voxxzy, con, cras=None):
        """
        This function creates a connectome affinity matrix among vertices,
        starting from an affinity matrix among voxels,
        by assignment from the nearest neighboring voxel to the respective vertex.
        :param verts: vertices' coordinates array (number of vertices x 3)
        :param vox: labels of connectome nodes-voxels (integers>=1)
        :param voxxzy: coordinates of the connectome nodes-voxels in ras space
        :param con: connectivity affinity matrix
        :param cras: center ras point to be optionally added to the vertices coordinates
                    (being probably in freesurfer tk-ras or surface ras coordinates) to align with the volume voxels
        :return: the affinity matrix among vertices
        """
        # Add the cras to take them to scanner ras coordinates, if necessary:
        if cras is not None:
            verts += numpy.repeat(numpy.expand_dims(cras, 1).T,
                                  verts.shape[0],
                                  axis=0)
        # TODO?: to use aparc+aseg to correspond vertices only to voxels of the same label
        # There would have to be a vertex->voxel of aparc+aseg of the same label -> voxel of tdi_lbl_in_T1 mapping
        # Maybe redundant  because we might be ending to the same voxel of tdi_lbl anyway...
        # Something to test/discuss...
        # Find for each vertex the closest voxel node in terms of euclidean
        # distance:
        v2n = numpy.argmin(cdist(verts, voxxzy, 'euclidean'), axis=1)
        # Assign to each vertex the integer identity of the nearest voxel node.
        v2n = vox[v2n]
        print("...surface component's vertices correspond to " +
              str(numpy.size(numpy.unique(v2n))) + " distinct voxel nodes")
        affinity = con[v2n - 1, :][:, v2n - 1]
        return affinity
Ejemplo n.º 9
0
    t1_path = os.path.join(mri_directory, t1_name)
    if not os.path.exists(t1_path):
        message = "File %s does not exist. Please change %s value to %s path." % (
            t1_path, MRI_DIRECTORY, T1_RAS_VOLUME)
        logger.error(message)
        raise Exception(message)

    if not os.path.exists(os.path.expandvars(CC_POINT_FILE)):
        message = "File %s does not exist." % CC_POINT_FILE
        logger.error(message)
        raise Exception(message)


if __name__ == "__main__":
    logger = get_logger(__name__)

    args = parse_arguments()
    abs_path = os.path.abspath(os.path.dirname(__file__))

    snapshots_directory = os.environ[SNAPSHOTS_DIRECTORY_ENVIRON_VAR]
    if snapshots_directory is "":
        snapshots_directory = SNAPSHOTS_DIRECTORY
        logger.warning(
            "There is no value assigned to %s environment variable. The snapshots will be in %s directory by default.",
            SNAPSHOTS_DIRECTORY_ENVIRON_VAR, SNAPSHOTS_DIRECTORY)

    snapshot_count = int(os.environ.get(SNAPSHOT_NUMBER_ENVIRON_VAR, 0))

    imageTransformer = ImageTransformer(abs_path)
    imageTransformer.use_ras_transform = args.ras_transform
Ejemplo n.º 10
0
class ImageWriter(object):
    logger = get_logger(__name__)

    contour_colors = ['y', 'r', 'b', 'g', 'm', 'c', 'w', 'k']
    volume_cmaps = ['gray', 'hot', 'jet']
    transparency = [0.3, 0.5]

    def __init__(self, snapshots_directory: os.PathLike):
        self.snapshots_directory = snapshots_directory

        if not os.path.exists(self.snapshots_directory):
            os.mkdir(self.snapshots_directory)

    def get_path(self, result_name: str):
        return os.path.join(self.snapshots_directory,
                            result_name + SNAPSHOT_EXTENSION)

    def write_matrix(self,
                     x: numpy.ndarray,
                     y: numpy.ndarray,
                     matrix: numpy.ndarray,
                     result_name: str,
                     cmap=volume_cmaps[0]):
        pyplot.pcolormesh(x, y, matrix, cmap=cmap)
        pyplot.axes().set_aspect('equal', 'datalim')
        pyplot.axis('off')
        pyplot.savefig(self.get_path(result_name),
                       bbox_inches='tight',
                       pad_inches=0.0)
        pyplot.clf()

    def write_2_matrices(self, x: numpy.ndarray, y: numpy.ndarray,
                         matrix_background: numpy.ndarray, x1: numpy.ndarray,
                         y1: numpy.ndarray, matrix_overlap: numpy.ndarray,
                         result_name: str):
        pyplot.pcolormesh(x, y, matrix_background, cmap=self.volume_cmaps[0])
        # masked = numpy.ma.masked_where(matrix_overlap < 0.9, matrix_overlap)
        pyplot.pcolormesh(x1,
                          y1,
                          matrix_overlap,
                          cmap=self.volume_cmaps[1],
                          alpha=self.transparency[0])
        pyplot.axes().set_aspect('equal', 'datalim')
        pyplot.axis('off')
        pyplot.savefig(self.get_path(result_name),
                       bbox_inches='tight',
                       pad_inches=0.0)
        pyplot.clf()

    def write_3_matrices(self, x: numpy.ndarray, y: numpy.ndarray,
                         matrix_background: numpy.ndarray, x1: numpy.ndarray,
                         y1: numpy.ndarray, matrix_overlap_1: numpy.ndarray,
                         x2: numpy.ndarray, y2: numpy.ndarray,
                         matrix_overlap_2: numpy.ndarray, result_name: str):
        pyplot.pcolormesh(x, y, matrix_background, cmap=self.volume_cmaps[0])
        pyplot.pcolormesh(x1,
                          y1,
                          matrix_overlap_1,
                          cmap=self.volume_cmaps[1],
                          alpha=self.transparency[0])
        pyplot.pcolormesh(x2,
                          y2,
                          matrix_overlap_2,
                          cmap=self.volume_cmaps[2],
                          alpha=self.transparency[1])
        pyplot.axes().set_aspect('equal', 'datalim')
        pyplot.axis('off')
        pyplot.savefig(self.get_path(result_name),
                       bbox_inches='tight',
                       pad_inches=0.0)

    def write_surface_with_annotation(self,
                                      surface: Surface,
                                      annot: Annotation,
                                      result_name: str,
                                      positions: list = [(0, 0), (0, 90),
                                                         (0, 180), (0, 270),
                                                         (90, 0), (270, 0)]):
        x = surface.vertices[:, 0]
        y = surface.vertices[:, 1]
        z = surface.vertices[:, 2]

        fig = pyplot.figure()

        ax = Axes3D(fig)

        min = numpy.min([numpy.min(x), numpy.min(y), numpy.min(z)])
        max = numpy.max([numpy.max(x), numpy.max(y), numpy.max(z)])

        ax.set_xlim3d(min, max)
        ax.set_ylim3d(min, max)
        ax.set_zlim3d(min, max)

        if annot is not None:
            face_colors = annot.compute_face_colors(surface.triangles)
            normals = surface.compute_normals()
            face_colors = ax._shade_colors(face_colors, normals)

        poly_line = ax.plot_trisurf(x, y, z, triangles=surface.triangles)

        if annot is not None:
            poly_line.set_edgecolor(face_colors)
            poly_line.set_facecolor(face_colors)

        pyplot.axis('off')

        snapshot_index = 0
        for e, a in positions:
            ax.view_init(elev=e, azim=a)
            ax.dist = 6
            pyplot.savefig(self.get_path(result_name + str(snapshot_index)),
                           dpi=fig.dpi)
            snapshot_index += 1

        self.logger.info("The 6 snapshots were generated")

    def save_figure(self, result_name: str):
        pyplot.axes().set_aspect('equal', 'datalim')
        pyplot.axis('off')
        pyplot.savefig(self.get_path(result_name),
                       bbox_inches='tight',
                       pad_inches=0.0)

    def write_matrix_and_surfaces(
            self, x_axis_coords: numpy.ndarray, y_axis_coords: numpy.ndarray,
            matrix_background: numpy.ndarray, surface_x_array: numpy.ndarray,
            surface_y_array: numpy.ndarray,
            surface_index: Union[numpy.ndarray, list], clear_flag: bool):
        if clear_flag:
            pyplot.clf()
            pyplot.pcolormesh(x_axis_coords,
                              y_axis_coords,
                              matrix_background,
                              cmap=self.volume_cmaps[0])
        color_index = surface_index % len(self.contour_colors)
        for contour in range(len(surface_x_array)):
            pyplot.plot(surface_x_array[contour][:],
                        surface_y_array[contour][:],
                        self.contour_colors[color_index])
Ejemplo n.º 11
0
    parser.add_argument("surface_path")
    parser.add_argument("output_path")
    parser.add_argument("-matrix_paths", nargs='+', default=[])
    parser.add_argument(
        "-ss", help='Create snapshots of the transformed surface', action="store_true")

    return parser.parse_args()


if __name__ == "__main__":
    args = parse_arguments()

    surface_path = os.path.expandvars(args.surface_path)
    output_path = os.path.expandvars(args.output_path)

    logger = get_logger(__name__)

    image_processor = ImageProcessor(snapshots_directory=os.environ[SNAPSHOTS_DIRECTORY_ENVIRON_VAR],
                                     snapshot_count=int(os.environ.get(SNAPSHOT_NUMBER_ENVIRON_VAR, 0)))
    generic_io = GenericIO()

    logger.info("The surface transformation process has began")
    surface_io = IOUtils.surface_io_factory(surface_path)
    surface = surface_io.read(surface_path, False)

    if len(args.matrix_paths) is not 0:
        transformation_matrices = []

        for transform_matrix_path in args.matrix_paths:
            transformation_matrices.append(
                numpy.array(generic_io.read_transformation_matrix(os.path.expandvars(transform_matrix_path))))
Ejemplo n.º 12
0
class GiftiSurfaceIO(ABCSurfaceIO):
    """
    This class reads content of GIFTI surface files
    """
    logger = get_logger(__name__)

    def read(self, data_file, use_center_surface):
        gifti_image = giftiio.read(data_file)
        image_metadata = gifti_image.meta.metadata
        self.logger.info("From the file %s the extracted metadata is %s",
                         data_file, image_metadata)

        data_arrays = gifti_image.darrays
        vertices = data_arrays[0].data
        triangles = data_arrays[1].data

        vol_geom_center_ras = [0, 0, 0]
        vertices_metadata = data_arrays[0].metadata
        self.logger.info("The metadata from vertices data array is %s",
                         vertices_metadata)
        vertices_coord_system = data_arrays[0].coordsys
        self.logger.info(
            "The coordinate system transform matrix from vertices data array is %s",
            vertices_coord_system)
        triangles_metadata = data_arrays[1].metadata
        self.logger.info("The metadata from triangles data array is %s",
                         triangles_metadata)

        if use_center_surface:
            vol_geom_center_ras = [0, 0, 0]
        else:
            vol_geom_center_ras[0] = float(
                vertices_metadata[CENTER_RAS_GIFTI_SURF[0]])
            vol_geom_center_ras[1] = float(
                vertices_metadata[CENTER_RAS_GIFTI_SURF[1]])
            vol_geom_center_ras[2] = float(
                vertices_metadata[CENTER_RAS_GIFTI_SURF[2]])

        return Surface(vertices,
                       triangles,
                       area_mask=None,
                       center_ras=vol_geom_center_ras,
                       vertices_coord_system=vertices_coord_system,
                       generic_metadata=image_metadata,
                       vertices_metadata=vertices_metadata,
                       triangles_metadata=triangles_metadata)

    def write(self, surface_obj, file_path):
        image_metadata = GiftiMetaData().from_dict(
            surface_obj.generic_metadata)
        vertices_metadata = GiftiMetaData().from_dict(
            surface_obj.vertices_metadata)
        triangles_metadata = GiftiMetaData().from_dict(
            surface_obj.triangles_metadata)

        gifti_image = GiftiImage()
        gifti_image.set_metadata(image_metadata)

        data = GiftiDataArray(surface_obj.vertices,
                              datatype='NIFTI_TYPE_FLOAT32',
                              intent='NIFTI_INTENT_POINTSET')
        data.meta = vertices_metadata
        data.coordsys = surface_obj.vertices_coord_system
        gifti_image.add_gifti_data_array(data)

        data = GiftiDataArray(surface_obj.triangles,
                              datatype='NIFTI_TYPE_INT32',
                              intent='NIFTI_INTENT_TRIANGLE')
        data.meta = triangles_metadata
        data.coordsys = None
        gifti_image.add_gifti_data_array(data)

        nibabel.save(gifti_image, file_path)

    def read_transformation_matrix_from_metadata(self, image_metadata):
        matrix_from_metadata = [[0, 0, 0, 0] for _ in range(4)]

        for i in range(3):
            for j in range(4):
                matrix_from_metadata[i][j] = float(
                    image_metadata[TRANSFORM_MATRIX_GIFTI_KEYS[i][j]])

        matrix_from_metadata[3] = [0.0, 0.0, 0.0, 1.0]
        return matrix_from_metadata

    def write_transformation_matrix(self, image_metadata):
        # we can temporary write the identity matrix to gifti meta to avoid
        # freeview rotations.

        identity_matrix = [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0],
                           [0.0, 0.0, 1.0, 0.0]]

        for i in range(3):
            for j in range(4):
                image_metadata[TRANSFORM_MATRIX_GIFTI_KEYS[i][j]] = str(
                    identity_matrix[i][j])
Ejemplo n.º 13
0
def write_dax(adag, dax_file_name):
    # Write the DAX to stdout
    logger = get_logger(__name__)
    logger.info("Writing DAX into %s: %s" % (dax_file_name, adag))
    with open(dax_file_name, "w") as f:
        adag.writeXML(f)
Ejemplo n.º 14
0
from enum import Enum
from tvb.recon.logger import get_logger

LOGGER = get_logger(__name__)


class ConfigKey(Enum):
    """
    Flow parameters --> These will influence the DAX generation
    """
    SUBJECT = "subject"
    T1_FRMT = "t1.format"
    T2_FLAG = "t2.flag"
    T2_FRMT = "t2.format"
    FLAIR_FLAG = "flair.flag"
    FLAIR_FRMT = "flair.format"
    OPENMP_THRDS = "openmp.threads"
    ATLAS = "parcelation.atlas"
    DWI_IS_REVERSED = "dwi.is.reversed"
    DWI_FRMT = "dwi.format"
    DWI_USE_GRADIENT = "dwi.use.gradient"
    DWI_MULTI_SHELL = "dwi.multi.shell"
    MRTRIX_THRDS = "mrtrix.threads"
    DWI_SCAN_DIRECTION = "dwi.scan.direction"
    ASEG_LH_LABELS = "aseg_lh_labels"
    ASEG_RH_LABELS = "aseg_rh_labels"
    USE_FLIRT = "use_flirt"
    STRMLNS_NO = "strmlns_no"
    STRMLNS_SIFT_NO = "strmlns_sift_no"
    STRMLNS_LEN = "strmlns_len"
    STRMLNS_STEP = "strmlns_step"
Ejemplo n.º 15
0
class MappingService(object):
    CORT_TYPE = "aparc"
    SUBCORT_TYPE = "aseg"

    FS_PREFIX_LH_DEFAULT = "ctx-lh-"
    FS_PREFIX_RH_DEFAULT = "ctx-rh-"
    FS_PREFIX_LH_A2009S = "ctx_lh_"
    FS_PREFIX_RH_A2009S = "ctx_rh_"

    UNKNOWN_REGION = "unknown"
    CORPUSCALLOSUM_REGION = "corpuscallosum"
    UNKNOWN_SUBCORTICAL_REGION = "Unknown"
    CORPUSCALLOSUM_REGION_A2009S = "Corpus_callosum"

    atlas_suffix = AtlasSuffix.DEFAULT
    fs_prefix_lh = FS_PREFIX_LH_DEFAULT
    fs_prefix_rh = FS_PREFIX_RH_DEFAULT

    logger = get_logger(__name__)

    def __init__(self, atlas_suffix: AtlasSuffix, cort_annot_lh: Annotation,
                 cort_annot_rh: Annotation, subcort_annot_lh: Annotation,
                 subcort_annot_rh: Annotation):
        self.atlas_suffix = atlas_suffix
        if atlas_suffix == AtlasSuffix.A2009S:
            self.fs_prefix_lh = self.FS_PREFIX_LH_A2009S
            self.fs_prefix_rh = self.FS_PREFIX_RH_A2009S

        self.cort_lut_dict = self.generate_lut_dict_from_annot(
            cort_annot_lh, cort_annot_rh, self.CORT_TYPE, 0)
        self.subcort_lut_dict = self.generate_lut_dict_from_annot(
            subcort_annot_lh, subcort_annot_rh, self.SUBCORT_TYPE,
            len(self.cort_lut_dict))
        self.cort_region_mapping = list()
        self.subcort_region_mapping = list()

    def get_lh_regions(self):
        return list(self.cort_lut_dict.keys())[0:self.len_lh]

    def get_rh_regions(self):
        return list(self.cort_lut_dict.keys())[self.len_rh:]

    def generate_lut_dict_from_annot(self, annot_lh: Annotation,
                                     annot_rh: Annotation, annot_type: str,
                                     idx: int) -> dict:
        dict_lh = self._get_dict_from_annot(annot_lh)
        dict_rh = self._get_dict_from_annot(annot_rh)

        if annot_type == self.CORT_TYPE:
            self.len_lh = len(dict_lh)
            self.len_rh = len(dict_rh)
            return self._prepare_cort_lut_dict(dict_lh, dict_rh, idx)

        return self._prepare_subcort_lut_dict(dict_lh, dict_rh, idx)

    def _get_dict_from_annot(self, annot: Annotation) -> dict:
        annot_dict = dict()
        vtx_rm = annot.region_mapping
        vtx_rm_unique_vals = numpy.unique(vtx_rm)

        region_names = annot.region_names
        for idx, region_name in enumerate(region_names):
            if self.atlas_suffix == AtlasSuffix.A2009S:
                if "&" in region_name:
                    region_names[idx] = region_name.replace("&", "_and_")

        for unwanted_region in (self.UNKNOWN_REGION,
                                self.UNKNOWN_SUBCORTICAL_REGION,
                                self.CORPUSCALLOSUM_REGION,
                                self.CORPUSCALLOSUM_REGION_A2009S):
            if unwanted_region in region_names and region_names.index(
                    unwanted_region) in vtx_rm_unique_vals:
                self.logger.warn("This annotation contains vertices for %s" %
                                 unwanted_region)

        region_names_to_keep = [
            region_names[idx] for idx in range(len(region_names))
            if idx in vtx_rm_unique_vals
        ]

        outside_range_values = list(
            set(vtx_rm_unique_vals) - set(range(len(region_names))))
        if len(outside_range_values) > 0:
            self.logger.warn(
                "This annotation contains vertices associated to values outside the interval [ %d, %d]"
                % (0, len(region_names) - 1))
            self.logger.warn("These values are: %s" % outside_range_values)
            self.logger.info(
                "Vertices mapped to these values will be mapped to %s" %
                self.UNKNOWN_REGION)
            if self.atlas_suffix == AtlasSuffix.A2009S:
                region_names_to_keep.append(self.UNKNOWN_SUBCORTICAL_REGION)
            else:
                region_names_to_keep.append(self.UNKNOWN_REGION)

        for idx, name in enumerate(region_names_to_keep):
            annot_dict[idx] = name

        return annot_dict

    def _prepare_cort_lut_dict(self, dict_lh: dict, dict_rh: dict,
                               idx: int) -> dict:
        lut_dict = dict()

        lut_dict.update({
            idx + key: self.fs_prefix_lh + val
            for (key, val) in dict_lh.items()
        })
        idx += len(lut_dict)
        lut_dict.update({
            idx + key: self.fs_prefix_rh + val
            for (key, val) in dict_rh.items()
        })

        return lut_dict

    def _prepare_subcort_lut_dict(self, dict_lh: dict, dict_rh: dict,
                                  idx: int) -> dict:
        lut_dict = dict()
        lut_dict.update({idx + key: val for (key, val) in dict_lh.items()})

        idx += len(lut_dict)
        lut_dict.update({idx + key: val for (key, val) in dict_rh.items()})

        return lut_dict

    def _invert_color_lut(self, color_lut_dict: dict) -> dict:
        inv_dict = {}
        for (key, val) in color_lut_dict.items():
            inv_dict.update({val: key})
        return inv_dict

    def generate_region_mapping_for_cort_annot(self, lh_annot: Annotation,
                                               rh_annot: Annotation):
        region_mapping = list()
        cort_inv_lut_dict = self._invert_color_lut(self.cort_lut_dict)

        lh_annot.region_mapping[lh_annot.region_mapping == -1] = 0
        rh_annot.region_mapping[rh_annot.region_mapping == -1] = 0

        self.lh_region_mapping = list()
        for lbl in lh_annot.region_mapping:
            current_region_name = lh_annot.region_names[lbl]
            region_mapping.append(
                cort_inv_lut_dict.get(self.fs_prefix_lh + current_region_name))
            self.lh_region_mapping.append(
                cort_inv_lut_dict.get(self.fs_prefix_lh + current_region_name))

        self.rh_region_mapping = list()
        for lbl in rh_annot.region_mapping:
            current_region_name = rh_annot.region_names[lbl]
            region_mapping.append(
                cort_inv_lut_dict.get(self.fs_prefix_rh + current_region_name))
            self.rh_region_mapping.append(
                cort_inv_lut_dict.get(self.fs_prefix_rh + current_region_name))

        self.cort_region_mapping = region_mapping

    def generate_region_mapping_for_subcort_annot(self, lh_annot: Annotation,
                                                  rh_annot: Annotation):
        region_mapping = list()
        subcort_inv_lut_dict = self._invert_color_lut(self.subcort_lut_dict)

        for annot in (lh_annot, rh_annot):
            for lbl in annot.region_mapping:
                region_mapping.append(
                    subcort_inv_lut_dict.get(annot.region_names[lbl]))

        self.subcort_region_mapping = region_mapping

    def is_cortical_region_mapping(self):
        return list(numpy.ones(len(self.cort_lut_dict), dtype=int)) + list(
            numpy.zeros(len(self.subcort_lut_dict), dtype=int))

    def get_all_regions(self):
        return list(self.cort_lut_dict.keys()) + list(
            self.subcort_lut_dict.keys())

    def get_entire_lut(self):
        dict = {}
        dict.update(self.cort_lut_dict)
        dict.update(self.subcort_lut_dict)
        return dict

    def get_mapping_for_aparc_aseg(self, lut_idx_to_name_dict: dict) -> dict:
        trg_names_labels_dict = self._invert_color_lut(self.cort_lut_dict)
        trg_names_labels_dict.update(
            self._invert_color_lut(self.subcort_lut_dict))

        src_to_trg = dict()
        for trg_name, trg_ind in trg_names_labels_dict.items():
            src_ind = lut_idx_to_name_dict.get(trg_name, None)
            if src_ind is not None:
                if trg_name == self.UNKNOWN_SUBCORTICAL_REGION:
                    self.logger.warn(
                        "The subcortical surfaces contain the region: %s and it will be mapped to -1 for aparc+aseg"
                        % self.UNKNOWN_SUBCORTICAL_REGION)
                    src_to_trg[src_ind] = -1
                else:
                    src_to_trg[src_ind] = trg_ind

        return src_to_trg

    def get_mapping_for_connectome_generation(self):
        non_zero_keys_dict = ({
            key + 1: val
            for (key, val) in self.get_entire_lut().items()
        })
        return non_zero_keys_dict
Ejemplo n.º 16
0
class FreeViewController(object):
    logger = get_logger(__name__)
    generic_io = GenericIO()

    target_screenshot_name = SNAPSHOT_NAME
    target_file = "slices.txt"
    cameraPositionsFileName = "cameraPositions.txt"
    in_point_file = "$SUBJ_DIR/scripts/ponscc.cut.log"
    point_line_flag = "CC-CRS"
    in_matrix_file = 'matrix.txt'

    folder_figures = os.environ[SNAPSHOTS_DIRECTORY_ENVIRON_VAR]

    def write_snapshot_camera_positions(self, projection):
        """
        TODO
        """
        count_number = int(os.environ[SNAPSHOT_NUMBER_ENVIRON_VAR])
        file_ref = open(self.cameraPositionsFileName, 'wb')
        png_path = self._get_image_name(count_number, projection, "1")
        file_ref.write("-cam Azimuth 0 Elevation 0 -ss %s\n" % png_path)
        png_path = self._get_image_name(count_number, projection, "2")
        file_ref.write("-cam Azimuth 90 Elevation 0 -ss %s\n" % png_path)
        png_path = self._get_image_name(count_number, projection, "3")
        file_ref.write("-cam Azimuth 180 Elevation 0 -ss %s\n" % png_path)
        png_path = self._get_image_name(count_number, projection, "4")
        file_ref.write("-cam Azimuth 270 Elevation 0 -ss %s\n" % png_path)
        png_path = self._get_image_name(count_number, projection, "5")
        file_ref.write("-cam Azimuth 0 Elevation 90 -ss %s\n" % png_path)
        png_path = self._get_image_name(count_number, projection, "6")
        file_ref.write("-cam Azimuth 0 Elevation 180 -ss %s\n" % png_path)
        file_ref.write(" -quit")
        file_ref.close()
        self.logger.info("It was written  " + self.cameraPositionsFileName)

    def _get_image_name(self, count_number, projection, suffix):
        return os.path.join(
            self.folder_figures, self.target_screenshot_name +
            str(count_number) + projection + suffix + SNAPSHOT_EXTENSION)

    def prepare_screenshot(self):
        matrix = self.generic_io.read_transformation_matrix(
            self.in_matrix_file)
        self.logger.info("Read idx2rsa matrix: %s" % matrix)

        vector = self.generic_io.read_cc_point(self.in_point_file,
                                               self.point_line_flag)
        self.logger.info("Read vector: %s" % vector)

        a = numpy.array(matrix)
        b = numpy.array(vector)
        ras_vector = a.dot(b)
        self.logger.info("Computed RAS vector: %s" % ras_vector)

        ras_string = ' '.join(map(str, ras_vector[:-1]))
        self._write_screenshot_command(self.target_file,
                                       self.target_screenshot_name, projection,
                                       ras_string)

    def _write_screenshot_command(self, file_path, shot_name, projection,
                                  ras_position):
        """
        Open slices.txt file and write the current screen-shot instruction: target_file_name and position
        """
        count_number = int(os.environ[SNAPSHOT_NUMBER_ENVIRON_VAR])
        file_ref = open(file_path, 'wb')
        png_path = os.path.join(
            self.folder_figures,
            shot_name + str(count_number) + projection + SNAPSHOT_EXTENSION)
        file_ref.write("-ras %s -ss %s" % (ras_position, png_path))
        file_ref.write(" -quit")
        file_ref.close()
        self.logger.info("It was written " + file_path)
Ejemplo n.º 17
0
class VolumeService(object):
    logger = get_logger(__name__)

    def __init__(self):
        self.annotation_service = AnnotationService()

    def gen_label_volume_from_labels_inds(self, values: Union[numpy.ndarray, list],
                                          input_label_volume_file: os.PathLike, output_label_volume_file: os.PathLike) \
            -> nibabel.nifti1.Nifti1Image:
        """
        This function creates a new volume matrix from an input label_volume matrix
        by setting values[i] to all voxels where label_volume == i
        """

        label_nii = nibabel.load(input_label_volume_file)
        label_volume = label_nii.get_data()

        new_volume = numpy.zeros(label_volume.shape)
        new_volume[:, :] = numpy.nan

        for i, value in enumerate(values):
            region = i + 1
            mask = label_volume[:, :, :] == region
            new_volume[mask] = value

        # TODO: I don't know what this is... I have to ask Viktor...
        def add_min_max(volume):
            """Ugly hack to deal with the MRtrix bug (?) that causes MRview to crop min/max values"""
            volume[0, 0, 0] = numpy.nanmin(volume) - 1
            volume[0, 0, 1] = numpy.nanmax(volume) + 1

        # add_min_max(new_volume)
        new_nii = nibabel.Nifti1Image(new_volume, label_nii.affine)
        nibabel.save(new_nii, output_label_volume_file)

        return new_nii

    def gen_label_volume_from_coords(self, values: Union[numpy.ndarray, list],
                                     coords: Union[os.PathLike, numpy.ndarray],
                                     labels: Union[os.PathLike, numpy.ndarray, list], ref_volume_file: os.PathLike,
                                     out_volume_file: os.PathLike, skip_missing: bool=False, dist: int=0) \
            -> numpy.ndarray:
        """
        # Create and save a new nifti label volume of similar shape to a reference volume
        # by setting input values at input positions (optionally + & - (int) dist)
        # after applying to them the ref_aff transform

        :param values: vector of values to be included in the output volume, numpy.ndarray of shape (n_vals, )
        :param coords: array or list of 3D point coordinates, numpy.ndarray of shape (n_coords, 3), or file to read array
        :param labels: array or list of labels'names, or file to read them
        :param ref_volume_file: path to nifti volume
        :param out_volume_file: file path for the output nifti volume to be written
        :param skip_missing: flag
        :param dist: integer indicating the size of the neighborhood around the coords' positions to be labeled
        :return: output nifti volume
        """
        ref_volume = nibabel.load(ref_volume_file)

        if os.path.isfile(str(labels)):
            labels = list(numpy.genfromtxt(labels, dtype=str, usecols=(0, )))
        else:
            labels = list(labels)

        if os.path.isfile(str(coords)):
            coords = numpy.genfromtxt(coords, dtype=float, usecols=(1, 2, 3))

        positions = numpy.zeros((len(values), 3))
        for i, label in enumerate(labels):
            try:
                contact_ind = labels.index(label)
                coord = coords[contact_ind, :]
            except ValueError:
                coord = numpy.array([numpy.nan, numpy.nan, numpy.nan])

            positions[i, :] = coord

        missing_mask = numpy.isnan(positions[:, 0])
        if skip_missing:
            values = values[~missing_mask]
            positions = positions[~missing_mask, :]
        else:
            if numpy.any(missing_mask):
                raise ValueError(
                    "Missing contact position(s) for: %s." % ", ".join([
                        name for name, missing in zip(labels, missing_mask)
                        if missing
                    ]))

            # numpy.array(names)[missing_mask]))

        new_volume = numpy.zeros(ref_volume.shape)
        # TODO: Find out the use of the following commented line:
        # new_volume[:, :] = numpy.nan

        kx, ky, kz = numpy.mgrid[-dist:dist + 1, -dist:dist + 1,
                                 -dist:dist + 1]

        for val, pos in zip(values, positions):
            ix, iy, iz = numpy.linalg.solve(ref_volume.affine,
                                            numpy.append(pos,
                                                         1.0))[0:3].astype(int)
            # new_volume[inds[0], inds[1], inds[2]] = val
            new_volume[ix + kx, iy + ky, iz + kz] = val

        # add_min_max(new_volume)

        new_nii = nibabel.Nifti1Image(new_volume, ref_volume.affine)
        nibabel.save(new_nii, out_volume_file)

        return nibabel.nifti1.Nifti1Image

    def vol_to_ext_surf_vol(self,
                            in_vol_path: os.PathLike,
                            labels: Optional[Union[numpy.ndarray,
                                                   list]] = None,
                            ctx: Optional[os.PathLike] = None,
                            out_vol_path: Optional[os.PathLike] = None,
                            labels_surf: Optional[Union[numpy.ndarray,
                                                        list]] = None,
                            labels_inner: str = '0'):
        """
        Separate the voxels of the outer surface of a structure, from the inner ones. Default behavior: surface voxels
        retain their label, inner voxels get the label 0, and the input file is overwritten by the output.
        """

        labels = self.annotation_service.read_input_labels(labels=labels,
                                                           ctx=ctx)
        number_of_labels = len(labels)
        # Set the labels for the surfaces
        if labels_surf is None:
            labels_surf = labels
        else:
            # Read the surface labels and make sure there is one for each label
            labels_surf = numpy.array(labels_surf.split()).astype('i')
            if len(labels_surf) == 1:
                labels_surf = numpy.repeat(labels_inner,
                                           number_of_labels).tolist()
            elif len(labels_surf) != number_of_labels:
                self.logger.warning(
                    "Output labels for surface voxels are neither of length "
                    "1 nor of length equal to the one of target labels.")
                return
            else:
                labels_surf = labels_surf.tolist()
        # Read the inner, non-surface labels
        labels_inner = numpy.array(labels_inner.split()).astype('i')
        # ...and make sure there is one for each label
        if len(labels_inner) == 1:
            labels_inner = numpy.repeat(labels_inner,
                                        number_of_labels).tolist()
        elif len(labels_inner) != number_of_labels:
            self.logger.warning(
                "Output labels for inner voxels are neither of length 1 nor "
                "of length equal to the one of the target labels.")
            return
        else:
            labels_inner = labels_inner.tolist()

        # Read the input volume...
        volume = IOUtils.read_volume(in_vol_path)

        # Neigbors' grid sharing a face
        eye3 = numpy.identity(3)
        border_grid = numpy.c_[eye3, -eye3].T.astype('i')
        n_border = 6

        out_volume = Volume(numpy.array(volume.data), volume.affine_matrix,
                            volume.header)

        # Initialize output indexes
        out_ijk = []

        for label_index in range(number_of_labels):
            current_label = labels[label_index]
            # Get the indexes of all voxels of this label:
            label_volxels_i, label_voxels_j, label_voxels_k = numpy.where(
                volume.data == current_label)
            # and for each voxel
            for voxel_index in range(label_volxels_i.size):
                # indexes of this voxel:
                current_voxel_i, current_voxel_j, current_voxel_k = \
                    label_volxels_i[voxel_index], label_voxels_j[
                        voxel_index], label_voxels_k[voxel_index]
                # Create the neighbors' grid sharing a face
                ijk_grid = border_grid + \
                           numpy.tile(numpy.array(
                               [current_voxel_i, current_voxel_j, current_voxel_k]), (n_border, 1))
                # Remove voxels outside the image
                indices_inside_image = numpy.all(
                    [(ijk_grid[:, 0] >= 0),
                     (ijk_grid[:, 0] < volume.dimensions[0]),
                     (ijk_grid[:, 1] >= 0),
                     (ijk_grid[:, 1] < volume.dimensions[1]),
                     (ijk_grid[:, 2] >= 0),
                     (ijk_grid[:, 2] < volume.dimensions[2])],
                    axis=0)
                ijk_grid = ijk_grid[indices_inside_image, :]
                try:
                    # If all face neighbors are of the same label...
                    if numpy.all(
                            volume.data[ijk_grid[:, 0], ijk_grid[:, 1],
                                        ijk_grid[:, 2]] == numpy.tile(
                                            volume.data[current_voxel_i,
                                                        current_voxel_j,
                                                        current_voxel_k], (
                                                            n_border, 1))):
                        # ...set this voxel to the corresponding inner target label
                        out_volume.data[
                            current_voxel_i, current_voxel_j,
                            current_voxel_k] = labels_inner[label_index]
                    else:
                        # ...set this voxel to the corresponding surface target label
                        out_volume.data[
                            current_voxel_i, current_voxel_j,
                            current_voxel_k] = labels_surf[label_index]
                        out_ijk.append([
                            current_voxel_i, current_voxel_j, current_voxel_k
                        ])
                except ValueError:  # empty grid
                    self.logger.error(
                        "Error at voxel ( %s, %s, %s ) of label %s: It appears to have no common-face "
                        "neighbors inside the image!", str(current_voxel_i),
                        str(current_voxel_j), str(current_voxel_k),
                        str(current_label))
                    return

        if out_vol_path is None:
            out_vol_path = in_vol_path

        IOUtils.write_volume(out_vol_path, out_volume)

        # save the output indexes that survived masking
        out_ijk = numpy.vstack(out_ijk)
        filepath = os.path.splitext(out_vol_path)[0]
        numpy.save(filepath + "-idx.npy", out_ijk)
        numpy.savetxt(filepath + "-idx.txt", out_ijk, fmt='%d')

    def mask_to_vol(self,
                    in_vol_path: os.PathLike,
                    mask_vol_path: os.PathLike,
                    out_vol_path: Optional[os.PathLike] = None,
                    labels: Optional[Union[numpy.ndarray, list]] = None,
                    ctx: Optional[str] = None,
                    vol2mask_path: Optional[os.PathLike] = None,
                    vn: int = 1,
                    th: float = 0.999,
                    labels_mask: Optional[os.PathLike] = None,
                    labels_nomask: str = '0'):
        """
        Identify the voxels that are neighbors within a voxel distance vn, to a mask volume, with a mask threshold of th
        Default behavior: we assume a binarized mask and set th=0.999, no neighbors search, only looking at the exact
        voxel position, i.e., vn=0. Accepted voxels retain their label, whereas rejected ones get a label of 0
        """

        # Set the target labels:
        labels = self.annotation_service.read_input_labels(labels=labels,
                                                           ctx=ctx)
        number_of_labels = len(labels)
        # Set the labels for the selected voxels
        if labels_mask is None:
            labels_mask = labels

        else:
            # Read the labels and make sure there is one for each label
            labels_mask = numpy.array(labels_mask.split()).astype('i')

            if len(labels_mask) == 1:
                labels_mask = numpy.repeat(labels_mask,
                                           number_of_labels).tolist()

            elif len(labels_mask) != number_of_labels:
                self.logger.warning(
                    "Output labels for selected voxels are neither of length 1 nor of length equal to "
                    "the one of target labels")
                return

            else:
                labels_mask = labels_mask.tolist()

        # Read the excluded labels and make sure there is one for each label
        labels_nomask = numpy.array(labels_nomask.split()).astype('i')
        if len(labels_nomask) == 1:
            labels_nomask = numpy.repeat(labels_nomask,
                                         number_of_labels).tolist()

        elif len(labels_nomask) != number_of_labels:
            self.logger.warning(
                "Output labels for excluded voxels are neither of length 1 nor of length equal to the "
                "one of the target labels")
            return

        else:
            labels_nomask = labels_nomask.tolist()

        volume = IOUtils.read_volume(in_vol_path)

        mask_vol = IOUtils.read_volume(mask_vol_path)

        # Compute the transform from vol ijk to mask ijk:
        ijk2ijk = numpy.identity(4)

        # If vol and mask are not in the same space:
        if os.path.exists(str(vol2mask_path)):
            # read the xyz2xyz transform and apply it to the inverse mask
            # affine transform to get an ijk2ijk transform.
            xyz2xyz = numpy.loadtxt(vol2mask_path)
            ijk2ijk = volume.affine_matrix.dot(
                numpy.dot(xyz2xyz, numpy.linalg.inv(mask_vol.affine_matrix)))

        # Construct a grid template of voxels +/- vn voxels around each ijk
        # voxel, sharing at least a corner
        grid = numpy.meshgrid(list(range(-vn, vn + 1, 1)),
                              list(range(-vn, vn + 1, 1)),
                              list(range(-vn, vn + 1, 1)),
                              indexing='ij')
        grid = numpy.c_[numpy.array(grid[0]).flatten(),
                        numpy.array(grid[1]).flatten(),
                        numpy.array(grid[2]).flatten()]
        n_grid = grid.shape[0]

        out_volume = Volume(numpy.array(volume.data), volume.affine_matrix,
                            volume.header)

        # Initialize output indexes
        out_ijk = []

        # For each target label:
        for label_index in range(number_of_labels):
            current_label = labels[label_index]
            # Get the indexes of all voxels of this label:
            label_voxels_i, label_voxels_j, label_voxels_k = numpy.where(
                volume.data == current_label)

            for voxel_index in range(label_voxels_i.size):
                current_voxel_i, current_voxel_j, current_voxel_k = \
                    label_voxels_i[voxel_index], label_voxels_j[
                        voxel_index], label_voxels_k[voxel_index]
                # TODO if necessary: deal with voxels at the edge of the image, such as brain stem ones...
                #     if any([(i==0), (i==mask_shape[0]-1),(j==0), (j==mask_shape[0]-1),(k==0), (k==mask_shape[0]-1)]):
                #               mask_shape[i,j,k]=0
                #               continue

                # ...get the corresponding voxel in the mask volume:
                ijk = numpy.round(
                    ijk2ijk.dot(
                        numpy.array([
                            current_voxel_i, current_voxel_j, current_voxel_k,
                            1
                        ]))[:3]).astype('i')

                # Make sure this point is within image limits
                for cc in range(3):
                    if ijk[cc] < 0:
                        ijk[cc] = 0

                    elif ijk[cc] >= mask_vol.dimensions[cc]:
                        ijk[cc] = mask_vol.dimensions[cc] - 1

                # If this is a voxel to keep, set it so...
                if mask_vol.data[ijk[0], ijk[1], ijk[2]] >= th:
                    out_volume.data[current_voxel_i, current_voxel_j,
                                    current_voxel_k] = labels_mask[label_index]
                    out_ijk.append(
                        [current_voxel_i, current_voxel_j, current_voxel_k])

                elif vn > 0:
                    # If not, and as long as vn>0 check whether any of its vn neighbors is a mask voxel.
                    # Generate the specific grid centered at the vertex ijk
                    ijk_grid = grid + numpy.tile(ijk, (n_grid, 1))

                    # Remove voxels outside the mask volume
                    indexes_within_limits = numpy.all(
                        [(ijk_grid[:, 0] >= 0),
                         (ijk_grid[:, 0] < mask_vol.dimensions[0]),
                         (ijk_grid[:, 1] >= 0),
                         (ijk_grid[:, 1] < mask_vol.dimensions[1]),
                         (ijk_grid[:, 2] >= 0),
                         (ijk_grid[:, 2] < mask_vol.dimensions[2])],
                        axis=0)
                    ijk_grid = ijk_grid[indexes_within_limits, :]

                    try:
                        # If none of these points is a mask point:
                        if (mask_vol.data[ijk_grid[:, 0], ijk_grid[:, 1],
                                          ijk_grid[:, 2]] < th).all():
                            out_volume.data[
                                current_voxel_i, current_voxel_j,
                                current_voxel_k] = labels_nomask[label_index]

                        else:  # if any of them is a mask point:
                            out_volume.data[
                                current_voxel_i, current_voxel_j,
                                current_voxel_k] = labels_mask[label_index]
                            out_ijk.append([
                                current_voxel_i, current_voxel_j,
                                current_voxel_k
                            ])

                    except ValueError:  # empty grid
                        self.logger.error(
                            "Error at voxel ( %s, %s, %s ): It appears to have no common-face neighbors "
                            "inside the image!", str(current_voxel_i),
                            str(current_voxel_j), str(current_voxel_k))
                        return

                else:
                    out_volume.data[
                        current_voxel_i, current_voxel_j,
                        current_voxel_k] = labels_nomask[label_index]

        if out_vol_path is None:
            out_vol_path = in_vol_path

        IOUtils.write_volume(out_vol_path, out_volume)

        # Save the output indexes that survived masking
        out_ijk = numpy.vstack(out_ijk)
        filepath = os.path.splitext(out_vol_path)[0]
        numpy.save(filepath + "-idx.npy", out_ijk)
        numpy.savetxt(filepath + "-idx.txt", out_ijk, fmt='%d')

    def vol_val_xyz(self, vol: numpy.ndarray, aff: numpy.ndarray,
                    val: float) -> numpy.ndarray:
        vox_idx = numpy.argwhere(vol == val)
        xyz = aff.dot(numpy.c_[vox_idx, numpy.ones(vox_idx.shape[0])].T)[:3].T
        return xyz

    def compute_label_volume_centers(self, label_volume: numpy.ndarray,
                                     affine: numpy.ndarray):

        for val in numpy.unique(label_volume):
            xyz = self.vol_val_xyz(label_volume, affine, val)
            x, y, z = xyz.mean(axis=0)
            yield val, (x, y, z)

    def label_with_dilation(self, to_label_nii_fname: os.PathLike,
                            dilated_nii_fname: os.PathLike,
                            out_nii_fname: os.PathLike):
        """
        Labels a volume using its labeled dilation. The dilated volume is labeled using scipy.ndimage.label function.
        :param to_label_nii_fname: usually a CT-mask.nii.gz
        :param dilated_nii_fname: dilated version of the to_label_nii_fname volume
        """

        # TODO could make dilation with ndimage also.
        mask = IOUtils.read_volume(to_label_nii_fname)
        dil_mask = IOUtils.read_volume(dilated_nii_fname)

        lab, n = scipy.ndimage.label(dil_mask.data)

        # TODO: this change is from tvb-make. Keep it or not? It returns a different result than the old version.
        lab_xyz = list(
            self.compute_label_volume_centers(lab, dil_mask.affine_matrix))
        lab_sort = numpy.r_[:n + 1]
        # sort labels along AP axis
        for i, (val, _) in enumerate(sorted(lab_xyz, key=lambda t: t[1][1])):
            lab_sort[val] = i
        lab = lab_sort[lab]

        mask.data *= lab
        self.logger.info('%d objects found when labeling the dilated volume.',
                         n)

        IOUtils.write_volume(out_nii_fname, mask)

    def _label_config(
            self,
            aparc: nibabel.nifti1.Nifti1Image) -> nibabel.nifti1.Nifti1Image:
        unique_data = numpy.unique(aparc.data)
        unique_data_map = numpy.r_[:unique_data.max() + 1]
        unique_data_map[unique_data] = numpy.r_[:unique_data.size]
        aparc.data = unique_data_map[aparc.data]
        return aparc

    def simple_label_config(self, in_aparc_path: os.PathLike,
                            out_volume_path: os.PathLike):
        """
        Relabel volume to have contiguous values like Mrtrix' labelconfig.
        :param in_aparc_path: volume voxel value is the index of the region it belongs to.
        :return: writes the labeled volume to out_volume_path.
        """

        aparc = IOUtils.read_volume(in_aparc_path)
        aparc = self._label_config(aparc)
        IOUtils.write_volume(out_volume_path, aparc)

    def _label_volume(self,
                      tdi_volume: nibabel.nifti1.Nifti1Image,
                      lo: float = 0.5) -> nibabel.nifti1.Nifti1Image:
        mask = tdi_volume.data > lo
        tdi_volume.data[~mask] = 0
        tdi_volume.data[mask] = numpy.r_[1:mask.sum() + 1]
        return tdi_volume

    def label_vol_from_tdi(self,
                           tdi_volume_path: os.PathLike,
                           out_volume_path: os.PathLike,
                           lo: float = 0.5):
        """
        Creates a mask of the voxels with tract ends > lo and any other voxels become 0.
        Labels each voxel different from 0 with integer labels starting from 1.
        :param tdi_volume_path: volume voxel value is the sum of tract ends. Voxel without tract ends has value 0.
        :param lo: tract ends threshold used for masking.
        :return: writes labeled volume to :ut_volume_path.
        """

        nii_volume = IOUtils.read_volume(tdi_volume_path)
        tdi_volume = self._label_volume(nii_volume, lo)
        IOUtils.write_volume(out_volume_path, tdi_volume)

    def remove_zero_connectivity_nodes(
            self,
            node_volume_path: os.PathLike,
            connectivity_matrix_path: os.PathLike,
            tract_length_path: Optional[str] = None):
        """
        It removes network nodes with zero connectivity from the volume and connectivity matrices.
        The zero connectivity nodes will be labeled with 0 in the volume and the remaining labels will be updated.
        The connectivity matrices will be symmetric.
        :param node_volume_path: tdi_lbl.nii volume path
        :param connectivity_matrix_path: .csv file, output of Mrtrix3 tck2connectome
        :param tract_length_path: optional .csv tract lengths matrix
        :return: overwrites the input volume and matrices with the processed ones. Also saves matrices as .npy.
        """

        node_volume = IOUtils.read_volume(node_volume_path)

        connectivity = numpy.array(
            numpy.genfromtxt(connectivity_matrix_path, dtype='int64'))
        connectivity = connectivity + connectivity.T
        connectivity_row_sum = numpy.sum(connectivity, axis=0)

        nodes_to_keep_indices = connectivity_row_sum > 0
        connectivity = connectivity[
            nodes_to_keep_indices, :][:, nodes_to_keep_indices]

        numpy.save(
            os.path.splitext(connectivity_matrix_path)[0] + NPY_EXTENSION,
            connectivity)
        numpy.savetxt(connectivity_matrix_path, connectivity, fmt='%1d')

        if os.path.exists(str(tract_length_path)):
            connectivity = numpy.array(
                numpy.genfromtxt(tract_length_path, dtype='int64'))
            connectivity = connectivity[
                nodes_to_keep_indices, :][:, nodes_to_keep_indices]

            numpy.save(
                os.path.splitext(tract_length_path)[0] + NPY_EXTENSION,
                connectivity)
            numpy.savetxt(tract_length_path, connectivity, fmt='%1d')

        else:
            self.logger.warning("Path %s is not valid.", tract_length_path)

        nodes_to_remove_indices, = numpy.where(~nodes_to_keep_indices)
        nodes_to_remove_indices += 1

        for node_index in nodes_to_remove_indices:
            node_volume.data[node_volume.data == node_index] = 0

        node_volume.data[node_volume.data > 0] = numpy.r_[1:(
            connectivity.shape[0] + 1)]

        IOUtils.write_volume(node_volume_path, node_volume)

    def con_vox_in_ras(
            self, ref_vol_path: os.PathLike) -> (numpy.ndarray, numpy.ndarray):
        """
        This function reads a tdi_lbl volume and returns the voxels that correspond to connectome nodes,
        and their coordinates in ras space, simply by applying the affine transform of the volume
        :param ref_vol_path: the path to the tdi_lbl volume
        :return: vox and voxxyz,
                i.e., the labels (integers>=1) and the coordinates of the connnectome nodes-voxels, respectively
        """
        # Read the reference tdi_lbl volume:
        vollbl = IOUtils.read_volume(ref_vol_path)
        vox = vollbl.data.astype('i')
        # Get only the voxels that correspond to connectome nodes:
        voxijk, = numpy.where(vox.flatten() > 0)
        voxijk = numpy.unravel_index(voxijk, vollbl.dimensions)
        vox = vox[voxijk[0], voxijk[1], voxijk[2]]
        # ...and their coordinates in ras xyz space
        voxxzy = vollbl.affine_matrix.dot(
            numpy.c_[voxijk[0], voxijk[1], voxijk[2],
                     numpy.ones(vox.shape[0])].T)[:3].T
        return vox, voxxzy

    def change_labels_of_aparc_aseg(self, atlas_suffix, volume, mapping_dict,
                                    conn_regs_nr):
        if atlas_suffix == AtlasSuffix.A2009S:
            volume.data[volume.data == 1000] = 11100
            volume.data[volume.data == 2000] = 12100
        not_matched = set()
        for i in range(volume.data.shape[0]):
            for j in range(volume.data.shape[1]):
                for k in range(volume.data.shape[2]):
                    val = volume.data[i][j][k]
                    if not val in mapping_dict:
                        not_matched.add(val)
                    volume.data[i][j][k] = mapping_dict.get(val, -1)

        print("Now values are in interval [%d - %d]" %
              (volume.data.min(), volume.data.max()))

        if not_matched:
            print("Not matched regions will be considered background: %s" %
                  not_matched)
        assert (volume.data.min() >= -1 and volume.data.max() < conn_regs_nr)

        return volume

    def transform_coords(self, coords: Union[list, numpy.ndarray, str], src_img: os.PathLike, dest_img: os.PathLike,
                  transform_mat: os.PathLike, output_file: Optional[str]=None) \
            -> (numpy.array, Union[numpy.ndarray, type(None)]):

        if os.path.isfile(coords):
            command = "img2imgcoord %s -mm -src %s -dest %s -xfm %s"
            args = [coords, src_img, dest_img, transform_mat]
        else:
            coords_str = " ".join([str(x) for x in coords])
            command = "echo %s | img2imgcoord -mm -src %s -dest %s -xfm %s"
            args = [coords_str, src_img, dest_img, transform_mat]

        if os.path.isdir(os.path.dirname(output_file)):
            command += " > %s"
            args.append(output_file)

        output, std_out, time = \
            execute_command(command % tuple(args), cwd=os.path.dirname(transform_mat), shell=True)

        transformed_coords_str = output.strip().split('\n')[-1]

        return numpy.array([
            float(x) for x in transformed_coords_str.split(" ") if x
        ]), output_file
Ejemplo n.º 18
0
class ImageTransformer(object):
    use_ras_transform = False
    use_center_surface = False
    use_cc_point = False
    converted_files_directory = "converted_files"
    created_files = []
    logger = get_logger(__name__)

    def __init__(self, path):
        self.converted_files_directory_path = os.path.join(
            path, self.converted_files_directory)
        if not os.path.exists(self.converted_files_directory_path):
            os.makedirs(self.converted_files_directory_path)

    def apply_transform(self, volume_path):
        if not self.use_ras_transform:
            return volume_path

        output_volume_path = os.path.join(
            self.converted_files_directory_path, 'ras' + basename(volume_path))

        try:
            x = subprocess.call(
                ['mri_convert', '--out_orientation', 'RAS', '--out_type', 'nii', '--input_volume', volume_path,
                 '--output_volume', output_volume_path])
            print(x)
            self.created_files.append(output_volume_path)
            return output_volume_path
        except subprocess.CalledProcessError:
            self.logger.error("Error converting volume")

    def center_surface(self, surface):
        if not self.use_center_surface:
            return surface

        surface_new_path = os.path.join(
            self.converted_files_directory_path, 'centered' + basename(surface))

        try:
            x = subprocess.call(
                ['mris_convert', '--to-scanner', surface, surface_new_path])
            print(x)
            self.created_files.append(surface_new_path)
            return surface_new_path
        except subprocess.CalledProcessError:
            self.logger.error("Error converting surface")

    def transform_single_volume(self, volume_path):
        return self.apply_transform(volume_path)

    def transform_2_volumes(self, background_path, overlay_path):
        return self.apply_transform(
            background_path), self.apply_transform(overlay_path)

    def transform_3_volumes(self, background_path,
                            overlay_1_path, overlay_2_path):
        return self.apply_transform(background_path), self.apply_transform(overlay_1_path), self.apply_transform(
            overlay_2_path)

    def transform_volume_surfaces(self, background_path, surfaces_list):
        new_surfaces_list = [self.center_surface(
            os.path.expandvars(surf)) for surf in surfaces_list]
        return self.apply_transform(background_path), new_surfaces_list

    def transform_volume_white_pial(
            self, background_path, resampled_surface, surfaces_path, use_gifti):
        if resampled_surface is not "":
            resampled_surface = "-" + resampled_surface

        gii = ""
        if use_gifti:
            gii = GIFTI_EXTENSION

        white_pial_surfaces_path = [hemi + "." + surface_type + resampled_surface + gii for hemi in ("rh", "lh") for
                                    surface_type in "pial", "white"]

        new_surfaces_list = [self.center_surface(os.path.expandvars(os.path.join(surfaces_path, surface))) for surface
                             in white_pial_surfaces_path]
        return self.apply_transform(background_path), new_surfaces_list
Ejemplo n.º 19
0
# -*- coding: utf-8 -*-

from Pegasus.DAX3 import Job, Link
from tvb.recon.logger import get_logger

LOGGER = get_logger(__name__)


def steps_dwi_preproc(main_dax, dwi_config):
    """
    :param main_dax: Append to main DAX DWI steps
    :param dwi_config: Patient based configuration
    """
    LOGGER.info("DWI pre-processing %s" % (dwi_config.folder_path, ))

    if dwi_config.is_dwi_reversed:
        LOGGER.info("Reversed DWI")

        if dwi_config.is_dicom:
            LOGGER.info("DICOM identified for DWI")
            job0 = Job(name="mrchoose",
                       node_label=dwi_config.prefix.upper() +
                       " input pre-processing 0 (Reversed, DICOM)")
            job0.addArguments("0", "mrconvert", dwi_config.folder,
                              dwi_config.dwi_raw_mif_file)
            job0.uses(dwi_config.folder, link=Link.INPUT)
            job0.uses(dwi_config.dwi_raw_mif_file,
                      link=Link.OUTPUT,
                      transfer=True,
                      register=False)
            main_dax.addJob(job0)
Ejemplo n.º 20
0
 def __init__(self, snapshots_directory: os.PathLike, snapshot_count: int=0):
     self.generic_io = GenericIO()
     self.writer = ImageWriter(snapshots_directory)
     self.snapshot_count = snapshot_count
     self.logger = get_logger(__name__)