Source code for AFQ.utils.streamlines

import os.path as op

import numpy as np
from dipy.io.stateful_tractogram import Origin, Space, StatefulTractogram
from dipy.io.streamline import load_tractogram, save_tractogram
from nibabel.affines import voxel_sizes
from nibabel.orientations import aff2axcodes

try:
    from trx.io import load as load_trx

[docs] has_trx = True
except ModuleNotFoundError: has_trx = False from AFQ.definitions.mapping import ConformedFnirtMapping from AFQ.utils.path import drop_extension, read_json
[docs] class SegmentedSFT: def __init__(self, bundles, sidecar_info=None): """ Initialize a SegmentedSFT object. Parameters ---------- bundles : dict A dictionary where keys are bundle names and values are either StatefulTractogram objects or dictionaries with keys "sl" and "idx". sidecar_info : dict, optional Sidecar information to optionally attach. """ if sidecar_info is None: sidecar_info = {}
[docs] self.sidecar_info = sidecar_info
[docs] self.bundle_names = []
sls = [] idxs = {} this_tracking_idxs = {} idx_count = 0 for b_name in bundles: if isinstance(bundles[b_name], dict): this_sft = bundles[b_name]["sl"] this_tracking_idxs[b_name] = bundles[b_name]["idx"] else: this_sft = bundles[b_name] this_sls = list(this_sft.streamlines) sls.extend(this_sls) new_idx_count = idx_count + len(this_sls) idxs[b_name] = np.arange(idx_count, new_idx_count, dtype=np.uint32) idx_count = new_idx_count self.bundle_names.append(b_name)
[docs] self.bundle_idxs = idxs
if len(this_tracking_idxs) > 1: self.this_tracking_idxs = this_tracking_idxs else: self.this_tracking_idxs = None self.sidecar_info["bundle_ids"] = {} dps = np.zeros(len(sls)) for ii, bundle_name in enumerate(self.bundle_names): self.sidecar_info["bundle_ids"][f"{bundle_name}"] = ii + 1 dps[self.bundle_idxs[bundle_name]] = ii + 1
[docs] self.sft = StatefulTractogram.from_sft( sls, this_sft, data_per_streamline={"bundle": dps} )
if self.this_tracking_idxs is not None: for kk, _vv in self.this_tracking_idxs.items(): self.this_tracking_idxs[kk] = ( self.this_tracking_idxs[kk].astype(int).tolist() ) self.sidecar_info["tracking_idx"] = self.this_tracking_idxs
[docs] def get_lengths(self): return self.sft._tractogram._streamlines._lengths
[docs] def to_rasmm(self): self.sft.to_space(Space.RASMM)
[docs] def get_bundle_idxs(self, b_name): return self.bundle_idxs[b_name]
[docs] def get_bundle(self, b_name): return self.sft[self.bundle_idxs[b_name]]
[docs] def get_bundle_param_info(self, b_name): return self.sidecar_info.get("Bundle Parameters", {}).get(b_name, {})
@classmethod
[docs] def fromfile(cls, trk_or_trx_file, reference="same", sidecar_file=None): if sidecar_file is None: sidecar_file = f"{drop_extension(trk_or_trx_file)}.json" if not op.exists(sidecar_file): raise ValueError( ( "JSON sidecars are required for trk files. " f"JSON sidecar not found for: {sidecar_file}" ) ) sidecar_info = read_json(sidecar_file) if trk_or_trx_file.endswith(".trx"): bundles = load_trx(trk_or_trx_file, reference) bundles.streamlines._data = bundles.streamlines._data.astype(np.float32) return SegmentedTRX(bundles, sidecar_info) else: sft = load_tractogram(trk_or_trx_file, reference, to_space=Space.RASMM) if reference == "same": reference = sft bundles = {} if "bundle_ids" in sidecar_info: for b_name, b_id in sidecar_info["bundle_ids"].items(): idx = np.where(sft.data_per_streamline["bundle"] == b_id)[0] bundles[b_name] = StatefulTractogram( sft.streamlines[idx], reference, Space.RASMM ) else: bundles["whole_brain"] = sft return SegmentedSFT(bundles, sidecar_info)
[docs] class SegmentedTRX(SegmentedSFT): def __init__(self, trx, sidecar_info=None): """ Initialize a SegmentedTRX object. Parameters ---------- trx : TrxFile The TRX file to interact with. sidecar_info : dict, optional Sidecar information to optionally attach. """ if sidecar_info is None: sidecar_info = {}
[docs] self.sidecar_info = sidecar_info
# Loading from TRX, we do not calculate additional information, # To avoid loading unnecessary streamlines into memory
[docs] self.bundle_names = list(trx.groups.keys())
[docs] self.sft = trx
# Mimic SFT attributes for compatibility affine = np.array(self.sft.header["VOXEL_TO_RASMM"], dtype=np.float32) dimensions = np.array(self.sft.header["DIMENSIONS"], dtype=np.uint16) vox_sizes = np.array(voxel_sizes(affine), dtype=np.float32) vox_order = "".join(aff2axcodes(affine)) space_attributes = (affine, dimensions, vox_sizes, vox_order) self.sft.space_attributes = space_attributes self.sft.space = Space.RASMM self.sft.origin = Origin.NIFTI self.sft.dtype_dict = self.sft.get_dtype_dict()
[docs] def get_lengths(self): return self.sft.streamlines._lengths
[docs] def to_rasmm(self): pass # always in RASMM
[docs] def get_bundle_idxs(self, b_name): return self.sft.groups[b_name]
[docs] def get_bundle(self, b_name): idx = self.sft.groups[b_name] return StatefulTractogram(self.sft.streamlines[idx], self.sft, Space.RASMM)
[docs] def split_streamline(streamlines, sl_to_split, split_idx): """ Given a Streamlines object, split one of the underlying streamlines Parameters ---------- streamlines : a Streamlines class instance The group of streamlines, one of which is being split. sl_to_split : int The index of the streamline that is being split split_idx : int Where is the streamline being split """ this_sl = streamlines[sl_to_split] streamlines._lengths = np.concatenate( [ streamlines._lengths[:sl_to_split], np.array([split_idx]), np.array([this_sl.shape[0] - split_idx]), streamlines._lengths[sl_to_split + 1 :], ] ) streamlines._offsets = np.concatenate( [np.array([0]), np.cumsum(streamlines._lengths[:-1])] ) return streamlines
[docs] def move_streamlines(tg, to, mapping, img, to_space=None, save_intermediates=None): """Move streamlines to or from template space. to : str Either "template" or "subject". This determines whether we will use the forward or backwards displacement field. mapping : DIPY or pyAFQ mapping Mapping to use to move streamlines. img : Nifti1Image Image defining reference for where the streamlines move to. to_space : Space or None If not None, space to move streamlines to after moving them to the template or subject space. If None, streamlines will be moved back to their original space. Default: None. save_intermediates : str or None If not None, path to save intermediate tractogram after moving to template or subject space. Default: None. """ tg_og_space = tg.space if isinstance(mapping, ConformedFnirtMapping): if to != "subject": raise ValueError( "Attempted to transform streamlines to template using " "unsupported mapping. " "Use something other than Fnirt." ) tg.to_vox() moved_sl = [] for sl in tg.streamlines: moved_sl.append(mapping.transform_pts(sl)) moved_sft = StatefulTractogram(moved_sl, img, Space.RASMM) else: tg.to_vox() if to == "template": moved_sl = mapping.transform_points(tg.streamlines) else: moved_sl = mapping.transform_points_inverse(tg.streamlines) moved_sft = StatefulTractogram(moved_sl, img, Space.VOX) if save_intermediates is not None: save_tractogram( moved_sft, op.join(save_intermediates, f"sls_in_{to}.trk"), bbox_valid_check=False, ) if to_space is None: moved_sft.to_space(tg_og_space) else: moved_sft.to_space(to_space) return moved_sft